Webhooks
Webhooks are how WhatIsUp.dev tells you about things — incoming messages, channel state changes, delivery acks. You register an HTTPS URL, we POST a signed JSON body whenever something happens.
How it flows
Every event is recorded in our delivery log before we attempt to send. That record carries the full payload, attempt count, and last response status. It's queryable via the dashboard or GET /v1/webhook-deliveries.
Signature
Every outbound webhook carries an X-WhatIsUp-Signature header in the same shape Stripe uses:
X-WhatIsUp-Signature: t=1700000000,v1=<hex_hmac>
The HMAC is computed over <timestamp>.<raw_body> with your endpoint's signing secret. You verify it by recomputing and comparing in constant time. The full how-to lives at Webhooks → Signature verification.
Retries and backoff
Failed deliveries (anything that's not 2xx, including timeouts) are retried with exponential backoff + jitter:
| Attempt | Delay |
|---|---|
| 1 (immediate) | 0s |
| 2 | ~10s |
| 3 | ~60s |
| 4 | ~5m |
| 5 | ~30m |
| 6 | ~2h |
| Final | gives up after attempt 6 |
Random jitter avoids thundering-herd retries when you ship a fix and a backlog drains. Total retry budget is ~2.5 hours.
Idempotency
Every event carries an event_id (ULID) that's stable across retries. Use it as your dedupe key:
async function handleWebhook(req) {
const eventId = req.body.event_id;
if (await alreadyProcessed(eventId)) return res.status(200);
// ... do the work ...
await markProcessed(eventId);
}Without dedupe, a slow ack from your side can cause the same event to be processed multiple times after a retry.
Per-host concurrency
The gateway caps how many requests can be in flight to any single host. If you have 50 channels all delivering to webhooks.example.com, only a handful fire at once; the rest queue. This keeps a slow endpoint from saturating delivery globally — your other endpoints keep flowing.
Payload retention
Delivery payloads are kept for 7 days for successful deliveries and 30 days for failed/retrying ones, then null'd by a sweeper. Metadata (status, attempt count, last response) sticks around for forensic queries. If you want longer payload retention, log it on your side.
Correlation IDs
Every webhook carries an X-WhatIsUp-Correlation-Id you can use to trace it back to whatever triggered it — usually the API request that produced the event, or a generated id for spontaneous events (incoming messages, state changes). Useful when you're trying to figure out which one of your test runs caused the spike in your logs.