Skip to content
WhatIsUp.dev

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

Incoming WhatsApp messages and state changes flow back to your endpoint.

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:

AttemptDelay
1 (immediate)0s
2~10s
3~60s
4~5m
5~30m
6~2h
Finalgives 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.