Skip to main content
Webhooks are the primary signal channel between Meta and Switchbord. Every inbound message, delivery status update, and template change arrives as a webhook delivery from Meta’s servers. Switchbord is designed so that no webhook delivery is ever silently discarded — every envelope is stored, every failure is inspectable, and every delivery can be replayed.

How Switchbord receives webhooks

Meta delivers webhook events to two endpoints on your Switchbord deployment:
  • POST /webhooks/whatsapp — the primary WhatsApp webhook receiver
  • POST /webhooks/meta — an alternate Meta webhook receiver
When you set up your channel, you register one of these URLs in Meta’s webhook configuration. Meta will then post events to that URL whenever a message is sent to your number, a message status changes, or a template is updated. Switchbord also accepts webhooks from external partner systems (such as Emarsys custom events) at compatibility endpoints, following the same store-first approach.

Webhook challenge verification

Before Meta starts sending events to a new webhook URL, it performs a challenge verification. Meta sends a GET request to your webhook URL with a hub.challenge parameter and expects you to echo it back. Switchbord handles this automatically at GET /webhooks/whatsapp. When you configure your META_WEBHOOK_VERIFY_TOKEN, Switchbord validates Meta’s token and responds with the challenge. You do not need to write any custom verification logic.
The challenge handshake only happens once when you register or update your webhook URL in Meta’s Business Manager. After that, all subsequent requests are POST deliveries.

Signature verification

Every POST webhook delivery from Meta includes an X-Hub-Signature-256 header. This header contains an HMAC-SHA256 signature computed over the raw request body using your Meta app’s signing secret. Switchbord verifies this signature on every delivery:
1

Read the raw body

Switchbord reads the raw request body before any parsing. The signature must be verified against the original bytes, not a re-serialized version.
2

Compute the expected signature

Switchbord computes HMAC-SHA256(raw_body, WEBHOOK_SIGNATURE_SECRET) and formats it as sha256=<hex>.
3

Compare signatures

Switchbord compares the computed signature against the value in X-Hub-Signature-256 using a constant-time comparison to prevent timing attacks.
4

Store and continue or reject

If the signature matches, the envelope is marked verified and queued for processing. If it does not match, the envelope is stored as a rejected delivery — it is not processed, but it is not discarded either.
Keep your WEBHOOK_SIGNATURE_SECRET secure and rotate it in Switchbord’s channel settings if you suspect it has been compromised. After rotation, update the secret in Meta’s Business Manager to match.

Envelope storage

After verification, Switchbord stores the complete envelope before doing anything else. The stored record includes:
  • The event source and topic (Meta, Emarsys, etc.)
  • The WABA ID and phone number scope
  • The verification result
  • Request headers and query parameters
  • A hash of the raw body (for integrity checks)
  • Response status and timing metadata
Storing the envelope first — before processing — means Switchbord can recover from processing failures without needing Meta to redeliver the event. The envelope is immutable once written; replays create new attempt records against the original envelope.

Event normalization

After storing the raw envelope, Switchbord normalizes the delivery into one of its known event families:
Event familyWhat it represents
message.inboundA new message received from a contact
message.statusA delivery status update (sent, delivered, read, failed)
template.updatedA template approval status change from Meta
conversation.updatedA conversation-level change such as a window opening or closing
The background worker uses this normalized event family to route the event to the correct processor. An message.inbound event creates or updates a conversation; a message.status event appends to the message’s delivery history.

Replay

If a webhook fails processing — for example, because a downstream dependency was temporarily unavailable — you can replay it from the operator dashboard.
1

Find the failed event

Navigate to the webhook events view and filter by status. Failed events show the error that caused the failure.
2

Inspect the envelope

Review the stored headers and body hash to confirm this is the event you want to reprocess.
3

Trigger the replay

Click Replay to queue the event for reprocessing. Switchbord creates a new attempt record against the original envelope — the original record is not mutated.
4

Confirm the result

The new attempt record shows whether reprocessing succeeded or failed, with the same diagnostics as the original attempt.
Replay is safe to run multiple times. Switchbord deduplicates by delivery ID, so replaying an event that was already successfully processed will not create duplicate messages, conversations, or status updates.
The operational endpoint POST /internal/webhooks/:id/replay exposes the same replay capability via API, which is useful for bulk recovery scripts during incident response.

Inspecting webhook events

The operator dashboard and internal API give you full visibility into webhook deliveries:
EndpointWhat it returns
GET /internal/webhooksPaginated list of webhook deliveries, filterable by type and status
GET /internal/webhooks/:idFull envelope details including headers, body hash, and normalization status
GET /internal/webhook-rejectionsDeliveries that failed signature verification
GET /internal/webhook-rejections/:idFull details for a specific rejected delivery
Use these endpoints to investigate missing messages, verify that Meta is delivering events correctly, and identify any deliveries that were rejected due to signature mismatches.

Performance expectations

Switchbord is designed to handle webhook ingestion with minimal latency. The target is a median response time under 250 milliseconds for the ingestion path, with fewer than 1% of requests taking longer than one second. The ingestion path does the minimum necessary work synchronously — verify, store, enqueue — and defers all processing to the background worker. This keeps the endpoint fast even under high volume.
Switchbord tracks rejection counts, replay counts, queue depth, and worker heartbeat. If ingestion latency or rejection rates spike, check the runtime diagnostics at GET /internal/runtime.