Skip to main content
Switchbord exposes two webhook ingress paths — /webhooks/whatsapp and /webhooks/meta — that behave identically except for the source label used when recording events. You register one of these URLs in the Meta App Dashboard, and Meta sends all WhatsApp Business Platform events there: inbound messages, delivery status updates, template status changes, and more. Switchbord validates every delivery, persists the raw envelope, normalizes the payload into a known event family, and enqueues a processing job.

GET /webhooks/whatsapp

Meta calls this endpoint with a one-time challenge before it starts sending events. You must complete this handshake before Meta will deliver any webhook payloads.
curl "https://api.your-switchbord.example.com/webhooks/whatsapp\
?hub.mode=subscribe\
&hub.verify_token=your-verify-token\
&hub.challenge=challenge_string_from_meta"
hub.mode
string
required
Must be subscribe for the verification handshake.
hub.verify_token
string
required
The token you configured in the Meta App Dashboard. Switchbord compares this against the meta-verify-token workspace secret.
hub.challenge
string
required
An arbitrary string that Meta expects you to echo back in the response body.
Responses:
StatusConditionBody
200Token matchesThe raw hub.challenge string (plain text, not JSON)
400Token mismatch or missing{"ok": false, "error": "verification_failed"}
Register the path (e.g., https://api.your-switchbord.example.com/webhooks/whatsapp) in the Meta App Dashboard under Webhooks. Set the verify token to the same value you store in your meta-verify-token workspace secret.

POST /webhooks/whatsapp

Meta sends all WhatsApp Business Platform events to this endpoint as POST requests.
# Simulate an inbound webhook (for local testing with mock mode)
curl -X POST http://localhost:3002/webhooks/whatsapp \
  -H "Content-Type: application/json" \
  -H "X-Hub-Signature-256: sha256=<computed_signature>" \
  -d '{
    "object": "whatsapp_business_account",
    "entry": [
      {
        "id": "WABA_ID",
        "changes": [
          {
            "value": {
              "messaging_product": "whatsapp",
              "metadata": {
                "display_phone_number": "15551234567",
                "phone_number_id": "PHONE_NUMBER_ID"
              },
              "messages": [
                {
                  "from": "15559876543",
                  "id": "wamid.ABC123",
                  "timestamp": "1717000000",
                  "type": "text",
                  "text": { "body": "Hello" }
                }
              ]
            },
            "field": "messages"
          }
        ]
      }
    ]
  }'

Request headers

X-Hub-Signature-256
string
required
HMAC-SHA256 signature of the raw request body, prefixed with sha256=. Switchbord validates this against your webhook-signing-secret workspace secret using a timing-safe comparison.
Content-Type
string
required
Must be application/json.
X-Meta-Delivery-Id
string
Unique delivery identifier from Meta. Used for deduplication.

Request body

The body must be a valid Meta webhook envelope:
object
string
required
Always "whatsapp_business_account" for WhatsApp events.
entry
array
required
Array of WABA entry objects.

What Switchbord does with your delivery

  1. Reads the raw body as text and computes the expected HMAC-SHA256 signature.
  2. Compares it to the X-Hub-Signature-256 header using a timing-safe comparison.
  3. Persists the raw envelope (headers, body hash, delivery ID, normalization status) regardless of signature validity.
  4. If the signature is valid, normalizes the envelope to extract messages and status updates, then enqueues a process_webhook_event outbox job.
  5. Returns HTTP 200 in all cases — Meta’s platform requires a 200 response to stop retrying; events with invalid signatures are recorded for operator review.

Responses

StatusCondition
200Delivery accepted and recorded (valid or invalid signature)
429Rate limit exceeded (secureWebhook guard)
Switchbord always returns 200 to Meta, even when signature validation fails. This prevents Meta from retrying legitimate deliveries due to transient mismatches. Invalid-signature events are recorded and visible in the webhook events list for operator review.
Success response body (valid signature):
{
  "accepted": true,
  "requestId": "3f8a1b2c-...",
  "signatureValid": true,
  "webhookEventId": "evt_01hx...",
  "outboxJobId": "job_01hx...",
  "summary": {
    "object": "whatsapp_business_account",
    "entries": 1,
    "changeCount": 1,
    "topic": "messages",
    "messageCount": 1,
    "statusCount": 0
  }
}
Response body (invalid signature, still 200):
{
  "accepted": false,
  "reason": "invalid_signature",
  "requestId": "7c2d4e9f-...",
  "webhookEventId": "evt_01hx...",
  "signatureValid": false,
  "summary": { ... }
}

POST /webhooks/meta

This path is an alternative ingress endpoint with the same behavior as POST /webhooks/whatsapp. The only difference is the source label attached to recorded webhook events ("meta" instead of "whatsapp"). Use whichever path you register in the Meta App Dashboard — both work identically.
curl -X POST https://api.your-switchbord.example.com/webhooks/meta \
  -H "Content-Type: application/json" \
  -H "X-Hub-Signature-256: sha256=<computed_signature>" \
  -d '{ "object": "whatsapp_business_account", "entry": [...] }'

Computing the signature

To generate a valid X-Hub-Signature-256 header for local testing, compute an HMAC-SHA256 of the raw request body using your WEBHOOK_SIGNING_SECRET:
import hmac
import hashlib

secret = "your-webhook-signing-secret"
body = '{"object":"whatsapp_business_account","entry":[...]}'

signature = hmac.new(
    secret.encode("utf-8"),
    body.encode("utf-8"),
    hashlib.sha256
).hexdigest()

header = f"sha256={signature}"
During local development, you can omit signature validation by leaving WEBHOOK_SIGNING_SECRET unset. The event will still be recorded, but signatureValid will be false.

Supported event topics

Switchbord normalizes inbound envelopes into one of the following topics based on the content of the entry changes:
TopicDescription
messagesOne or more inbound messages from a WhatsApp user
message_statusDelivery, read, or failure status updates for outbound messages
unknownChanges that do not match a recognized field
invalid_jsonThe request body could not be parsed as JSON
invalid_payloadThe parsed body did not match the expected webhook envelope shape
The topic is stored on the webhook event record and used for filtering in the operator API.