# Honeycomb Webhooks

Honeycomb (observability) can notify a webhook when a Trigger fires, an SLO burn alert fires or
Anomaly Detection raises a signal. Webhooks are **not signed**. Every request carries the webhook
integration's **Shared Secret** as plain text in the `X-Honeycomb-Webhook-Token` header, and your
endpoint authenticates the request by comparing that header with the secret you stored.

> **Hookdeck setup note:** Set a Shared Secret on the Honeycomb webhook. Webhooks without one send nothing to verify.

A static shared credential authenticates the sender, not the body, and gives no replay protection
(no signature, no timestamp). Use HTTPS and rotate the Shared Secret if it leaks.

## When to Use This Skill

* How do I receive Honeycomb webhooks?
* How do I verify the `X-Honeycomb-Webhook-Token` header?
* Does Honeycomb sign webhook payloads? (No: use the Shared Secret)
* How do I handle Honeycomb trigger, SLO burn alert or anomaly notifications?
* How do I deduplicate Honeycomb deliveries with `X-Honeycomb-Webhook-Delivery-ID`?
* Why does my Honeycomb webhook endpoint return 401?

## Verification (core)

```javascript
const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifyHoneycombRequest(headers, secret = process.env.HONEYCOMB_WEBHOOK_SECRET) {
  if (!secret) return false;                              // never accept when unconfigured
  const received = headers['x-honeycomb-webhook-token'];  // whole value, no prefix
  return Boolean(received) && safeEqual(received, secret);
}

```

* Header: `X-Honeycomb-Webhook-Token: <Shared Secret>`, sent on every request when a secret is set.
  Look it up case-insensitively and compare the **whole** value.
* Return `401` on a missing header or mismatch, for every HTTP method, before touching the body.
* Read the body raw (`express.raw({ type: '*/*' })`) and parse it only after the check. The body is
  `application/json`, but an empty body must not break the handler.
* Python: `hmac.compare_digest` on bytes (encode both sides; never pass `str`, which raises on
  non-ASCII).

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/honeycomb-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/honeycomb-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/honeycomb-webhooks/examples/fastapi/).

## HTTP Methods

Honeycomb's docs don't name the HTTP method, so the examples accept **GET, POST, PUT, PATCH and
DELETE** on the webhook route and check the credential first for each. Hookdeck's Honeycomb source
pre-selects POST without managing the method, so any other method Honeycomb uses must also be
allowed on the source.

## Alert Types

Honeycomb has no event-type field. The notification kind comes from where the webhook is attached:

| Kind | Sent When |
| --- | --- |
| Trigger | A Trigger fires or resolves |
| SLO burn alert | A Budget Rate or Exhaustion Time burn alert fires |
| Anomaly Detection | An anomaly signal fires |

The default payload carries the result of the alert as JSON but isn't documented field by field,
and a webhook can use a custom JSON template per alert type (Payload tab). The examples therefore
classify defensively by the fields present (`trigger`, `slo_burn_alert`, `anomaly`, else
`unknown`) and use clearly labelled **illustrative** payloads. Check a real delivery from your own
webhook before relying on specific fields.

## Idempotency and Timeouts

* `X-Honeycomb-Webhook-Delivery-ID` is unique per delivery and **stable across retries**: use it to
  deduplicate.
* Respond within **15 seconds**; a timeout fails the delivery with no retry. Do slow work
  asynchronously.
* Honeycomb retries up to three times on 408, 502, 429/503 with `Retry-After`, and connection
  failures. Other 4xx and 5xx fail without a retry, so a `401` is not retried.

## Environment Variables

```bash
HONEYCOMB_WEBHOOK_SECRET=your-shared-secret   # Shared Secret from the Honeycomb webhook integration

```

## Local Development

Honeycomb can't reach `localhost`. Tunnel with the Hookdeck CLI (no account required):

```bash
npx hookdeck-cli listen 3000 honeycomb --path /webhooks/honeycomb

```

Use the printed URL as the Webhook URL in Honeycomb.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/honeycomb-webhooks/references/overview.md) - What Honeycomb webhooks are, alert kinds and payload shape
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/honeycomb-webhooks/references/setup.md) - Add the webhook integration and set the Shared Secret
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/honeycomb-webhooks/references/verification.md) - Shared Secret check details, gotchas, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/honeycomb-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests