# WhatsApp Webhooks

Receive webhooks from the WhatsApp Business Platform (Cloud API), delivered by
Meta's Graph API. WhatsApp webhooks are Meta webhooks: they require a one-time
GET verification handshake and sign every POST with `X-Hub-Signature-256`.
They do not follow the Standard Webhooks spec.

## When to Use This Skill

* How do I receive WhatsApp webhooks?
* How do I complete the WhatsApp / Meta webhook verification handshake (`hub.challenge`)?
* How do I verify the WhatsApp `X-Hub-Signature-256` signature?
* Why is my WhatsApp webhook signature verification failing?
* How do I handle inbound WhatsApp messages vs. message status updates?

## Two Things Every Endpoint Must Do

1. GET handshake — When you register the endpoint, Meta sends a `GET` with
  `hub.mode=subscribe`, `hub.verify_token`, and `hub.challenge`. If the mode is
  `subscribe` and the token matches your configured verify token, respond `200`
  with the raw `hub.challenge` value as the body (no JSON, no quotes).
2. POST signature check — Every event `POST` carries
  `X-Hub-Signature-256: sha256=<hex>`. Compute HMAC-SHA256 over the raw request
  body using your app secret and compare timing-safe.

## Verification (core)

Compute HMAC-SHA256 over the raw bytes of the request body keyed on your Meta
app secret, then compare against the hex digest after `sha256=`. Use the raw
body exactly as received — Meta escapes non-ASCII characters (e.g. `é`), so
re-serializing parsed JSON produces a different, failing digest.

Node:

```javascript
const crypto = require('crypto');

function verifyWhatsAppSignature(rawBody, signatureHeader, appSecret) {
  const [algo, sig] = (signatureHeader || '').split('=');
  if (algo !== 'sha256' || !sig) return false;
  const expected = crypto.createHmac('sha256', appSecret).update(rawBody).digest('hex');
  try {
    return crypto.timingSafeEqual(Buffer.from(sig, 'hex'), Buffer.from(expected, 'hex'));
  } catch {
    return false; // length mismatch = invalid
  }
}

```

Python:

```python
import hmac, hashlib

def verify_whatsapp_signature(raw_body: bytes, signature_header: str, app_secret: str) -> bool:
    algo, _, sig = (signature_header or "").partition("=")
    if algo != "sha256" or not sig:
        return False
    expected = hmac.new(app_secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(sig, expected)

```

> Meta's official `whatsapp` Node SDK is built for sending messages via the
> Cloud API; it does not expose webhook HMAC verification, so verify manually with
> the standard algorithm above (see [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/whatsapp-webhooks/references/verification.md)).

> For complete handlers with the GET handshake, event dispatch, and tests, see:
> 
> * [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/whatsapp-webhooks/examples/express/)
> * [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/whatsapp-webhooks/examples/nextjs/)
> * [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/whatsapp-webhooks/examples/fastapi/)

## Payload Shape

Every event is wrapped under the `whatsapp_business_account` object. The `field`
property names the subscription (it is not a dotted event name):

```json
{
  "object": "whatsapp_business_account",
  "entry": [{
    "id": "<WABA_ID>",
    "changes": [{
      "field": "messages",
      "value": {
        "messaging_product": "whatsapp",
        "metadata": { "phone_number_id": "..." },
        "messages": [ { "from": "...", "id": "wamid...", "type": "text", "text": { "body": "Hi" } } ],
        "statuses": [ { "id": "wamid...", "status": "delivered", "recipient_id": "..." } ]
      }
    }]
  }]
}

```

Dispatch by iterating `entry[].changes[]` and branching on `change.field`. For the
`messages` field, inbound user messages arrive in `value.messages[]` and
outbound status updates arrive in `value.statuses[]` — the same field carries both.

## Common Subscription Fields & Events

| `field` | Contains | Notes |
| --- | --- | --- |
| `messages` | `value.messages[]` | Inbound messages: `text`, `image`, `audio`, `video`, `document`, `sticker`, `location`, `contacts`, `interactive`, `button`, `reaction`, `order`, `system` |
| `messages` | `value.statuses[]` | Outbound delivery receipts: `sent`, `delivered`, `read`, `failed` |
| `message_template_status_update` | `value` | Template approved / rejected / paused |
| `account_update` | `value` | Business account changes, bans, verification |
| `phone_number_quality_update` | `value` | Phone number quality rating changes |

> Full reference: [Webhook messages component](https://developers.facebook.com/documentation/business-messaging/whatsapp/webhooks/reference/messages)

## Environment Variables

```bash
WHATSAPP_APP_SECRET=your_meta_app_secret       # App Dashboard > App Settings > Basic > App Secret
WHATSAPP_VERIFY_TOKEN=your_own_random_string   # You choose this; must match the dashboard value

```

## Local Development

```bash
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 whatsapp --path /webhooks/whatsapp

```

## Gotchas

* Verify over the raw body — Meta escapes unicode; re-serialized JSON fails.
* Dedupe by message/event id — retries (up to 7 days, decreasing frequency) go
  to every subscribed app, and updates may batch up to 1000 entries per POST
  (payloads up to 3 MB).
* Two secrets — the app secret signs POSTs; the verify token is only for the
  GET handshake. They are different values.
* Live mode — some webhooks only fire when the app is in Live mode, and a valid
  TLS certificate is required.

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/whatsapp-webhooks/references/overview.md) - WhatsApp webhook concepts and events
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/whatsapp-webhooks/references/setup.md) - Configure the endpoint in the Meta App Dashboard
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/whatsapp-webhooks/references/verification.md) - WhatsApp-specific verification notes; links to the canonical Meta Graph API algorithm (shared with facebook-webhooks)