# Facebook Webhooks

Facebook webhooks are delivered through the Meta Graph API and are shared by
Facebook Pages, Instagram, Messenger, WhatsApp, and other Meta products. They do
not follow the Standard Webhooks spec.

> Using WhatsApp? The WhatsApp Business Platform shares this exact Meta
> mechanism but has its own events, payloads, and setup — use the dedicated
> [whatsapp-webhooks](/webhooks/skills/whatsapp-webhooks)
> skill. This skill covers Facebook Pages, Instagram, and Messenger. The shared
> handshake + `X-Hub-Signature-256` algorithm is documented once, canonically, in
> [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/facebook-webhooks/references/verification.md).

## When to Use This Skill

* How do I receive Facebook (Meta Graph API) webhooks?
* How do I complete the Facebook GET verification handshake (hub.challenge)?
* How do I verify Facebook webhook signatures with X-Hub-Signature-256?
* Why is my Facebook webhook signature verification failing?
* How do I handle Page `feed`, `mention`, Instagram `comments`, or Messenger `messages` events?

## Two Requests, Two Jobs

Facebook uses one endpoint for two different HTTP methods:

1. `GET` — verification handshake (one-time, on registration). Meta sends
  `hub.mode=subscribe`, `hub.verify_token`, and `hub.challenge` as query
  params. If `hub.verify_token` matches the Verify Token you set in the App
  Dashboard, echo back `hub.challenge` as a `200` plain-text response.
2. `POST` — event delivery. Meta sends a JSON body `{ object, entry[] }`
  and signs it with `X-Hub-Signature-256`.

## Verification (core)

Meta signs the raw request body with HMAC-SHA256 keyed on your App
Secret and sends the digest in `X-Hub-Signature-256` as `sha256=<hex>`. Verify
over the raw bytes before JSON parsing — Meta signs an escaped-unicode form
of the payload, so a re-serialized JSON string will not match. (The legacy
`X-Hub-Signature` header carries SHA-1 — prefer the SHA-256 header.)

Node:

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

function verify(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;
  }
}

```

Python:

```python
import hmac, hashlib

def verify(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)

```

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

## Common Event Types

Facebook events are (object, field) pairs, not dotted names. The top-level
`object` names the product; each `entry[].changes[].field` names what changed.

| Object | Field | Triggered When |
| --- | --- | --- |
| `page` | `feed` | Post, comment, like, or reaction on the Page |
| `page` | `mention` | The Page is mentioned in a post or comment |
| `page` | `messages` | A person sends a message to the Page (Messenger) |
| `instagram` | `comments` | A comment is added to an Instagram media object |
| `instagram` | `mentions` | The Instagram account is @mentioned |
| `user` | `feed` | An update is posted to the user's feed |
| `permissions` | — | A user grants or revokes a permission |

> For the full list, see [Meta Webhooks Reference](https://developers.facebook.com/docs/graph-api/webhooks/reference).

## Payload Structure

```json
{
  "object": "page",
  "entry": [
    {
      "id": "<page-id>",
      "time": 1458692752,
      "changes": [
        { "field": "feed", "value": { "item": "comment", "verb": "add" } }
      ]
    }
  ]
}

```

* A single POST can batch up to 1000 updates across `entry[]` — always
  iterate `entry[]` and handle each individually.
* Messenger deliveries carry a `messaging` array on each entry instead of
  `changes`.
* Respond `200 OK` quickly. Failed deliveries are retried immediately, then with
  decreasing frequency for up to 36 hours, after which they are dropped.

## Important Headers

| Header | Description |
| --- | --- |
| `X-Hub-Signature-256` | HMAC SHA-256 of the raw body, `sha256=<hex>` (use this) |
| `X-Hub-Signature` | Legacy HMAC SHA-1 signature (avoid) |

## Environment Variables

```bash
FACEBOOK_APP_SECRET=your_app_secret       # App Dashboard → Settings → Basic → App Secret
FACEBOOK_VERIFY_TOKEN=your_verify_token   # A string you choose; must match the Dashboard Verify Token

```

## Local Development

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

```

Use the tunnel URL as the Callback URL in App Dashboard → Webhooks. Note:
apps in Development mode only receive test notifications, and Page
subscriptions also require the `pages_manage_metadata` permission granted via
`POST /{page-id}/subscribed_apps`.

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/facebook-webhooks/references/overview.md) - Facebook/Meta webhook concepts and common events
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/facebook-webhooks/references/setup.md) - App Dashboard configuration, App Secret, Verify Token, subscribing Pages
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/facebook-webhooks/references/verification.md) - Handshake and signature verification details