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 before it
1 (immediate)0s
2~5s
3~10s
4~20s
5–10doubling: ~40s … ~21m
11~43m
12–18~1h each
Finalgives up after attempt 18

Every delay is jittered ±20% so retries don't arrive in lockstep when you ship a fix and a backlog drains. Total retry budget is ~8 hours. A 4xx other than 408/429 is final and not retried.

Idempotency

Every event carries an event_id (UUID) that's stable across retries — and, for message events (message.*, contact.resolved), across WhatsApp sending the same message or receipt again after a reconnect. 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.