Skip to main content
The Meta WhatsApp Business API (WABA) sends events directly to an endpoint you register in the Facebook App. When you connect a WhatsApp number in Timely.ai, our platform receives those events, processes them, and transforms them into the workspace’s internal webhooks. This page documents the raw format that Meta sends — useful for debugging, direct integrations, or when you need to understand what arrives before any transformation.

Configure the webhook in the Facebook App

1

Access the Meta App Dashboard

Go to developers.facebook.com → your App → WhatsAppSettingsWebhook.
2

Enter the callback URL

Paste the URL of the endpoint that will receive the events. In production, always use HTTPS with a valid certificate.If you are testing locally, use ngrok or Hookdeck to expose your server:
ngrok http 3000
# Forwarding: https://abc123.ngrok.io -> localhost:3000
3

Set the Verify Token

Choose a random, secure string (e.g. my_secret_token_123). Meta makes a GET request to your endpoint at setup time with the parameters hub.mode, hub.verify_token, and hub.challenge. Your server must respond with the value of hub.challenge if the token matches.
Node.js
app.get("/webhook/whatsapp", (req, res) => {
  const mode = req.query["hub.mode"];
  const token = req.query["hub.verify_token"];
  const challenge = req.query["hub.challenge"];

  if (mode === "subscribe" && token === process.env.VERIFY_TOKEN) {
    res.status(200).send(challenge);
  } else {
    res.sendStatus(403);
  }
});
4

Select fields

Enable at least messages to receive messages and statuses. Other useful fields: message_template_status_update, phone_number_quality_update.

Difference between Meta webhook and Timely webhook

AspectMeta webhook (raw)Timely webhook (platform)
SourceMeta → your serverTimely → your server
AuthenticationX-Hub-Signature-256 with App SecretX-Timely-Signature with your secret
FormatMeta envelope with entry[].changes[]Normalized payload by event type
Contact resolvedNo — only the wa_idYes — includes Timely contact_id
TransformationNone — raw Meta dataEnriched with workspace context
If you use Timely as an intermediary (the default case), you do not need to register a webhook with Meta — Timely handles that for you. This documentation is for cases where you connect WABA directly or need to debug the full flow.

Signature validation

Meta signs each request with the App Secret of your Facebook App using HMAC-SHA256. The header sent is X-Hub-Signature-256.
Node.js
import crypto from "crypto";

const APP_SECRET = process.env.META_APP_SECRET!;

function verifyMetaSignature(rawBody: Buffer, signature: string): boolean {
  const expected = "sha256=" + crypto
    .createHmac("sha256", APP_SECRET)
    .update(rawBody)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

app.post("/webhook/whatsapp", (req, res) => {
  const sig = req.headers["x-hub-signature-256"] as string;
  if (!verifyMetaSignature(req.rawBody, sig)) {
    return res.sendStatus(401);
  }
  // process the event...
  res.sendStatus(200);
});
Never process a Meta webhook without validating X-Hub-Signature-256. The App Secret is in your Facebook App dashboard — never expose it in source code or logs.

Payload: received text message

{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "WABA_ID",
      "changes": [
        {
          "value": {
            "messaging_product": "whatsapp",
            "metadata": {
              "display_phone_number": "5511999990000",
              "phone_number_id": "PHONE_NUMBER_ID"
            },
            "contacts": [
              {
                "profile": { "name": "João Silva" },
                "wa_id": "5511988887777"
              }
            ],
            "messages": [
              {
                "from": "5511988887777",
                "id": "wamid.HBgLNTUxMTk4ODg4Nzc3FQIAERgSMjRBOTQ5MzJBMTIzNDU2Nzg5AA==",
                "timestamp": "1713456789",
                "text": { "body": "Olá, quero saber sobre os planos." },
                "type": "text"
              }
            ]
          },
          "field": "messages"
        }
      ]
    }
  ]
}

Payload: sent message status

Statuses arrive when the message changes state: sentdeliveredread.
{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "WABA_ID",
      "changes": [
        {
          "value": {
            "messaging_product": "whatsapp",
            "metadata": {
              "display_phone_number": "5511999990000",
              "phone_number_id": "PHONE_NUMBER_ID"
            },
            "statuses": [
              {
                "id": "wamid.HBgLNTUxMTk4ODg4Nzc3FQIAERgSMjRBOTQ5MzJBMTIzNDU2Nzg5AA==",
                "status": "delivered",
                "timestamp": "1713456800",
                "recipient_id": "5511988887777",
                "conversation": {
                  "id": "CONVERSATION_ID",
                  "expiration_timestamp": "1713543200",
                  "origin": { "type": "user_initiated" }
                },
                "pricing": {
                  "billable": true,
                  "pricing_model": "CBP",
                  "category": "user_initiated"
                }
              }
            ]
          },
          "field": "messages"
        }
      ]
    }
  ]
}

Payload: media message (image, audio, document)

{
  "messages": [
    {
      "from": "5511988887777",
      "id": "wamid.EXEMPLO",
      "timestamp": "1713456900",
      "type": "image",
      "image": {
        "caption": "Segue o comprovante",
        "mime_type": "image/jpeg",
        "sha256": "abc123...",
        "id": "MEDIA_ID"
      }
    }
  ]
}
To download the media, make a GET https://graph.facebook.com/v19.0/{MEDIA_ID} request with your access token. Timely.ai performs this download automatically and stores the public URL in the media_url field of the normalized message.

Key payload fields

FieldTypeDescription
entry[].idstringWhatsApp Business Account ID
changes[].value.metadata.phone_number_idstringPhone number ID at Meta
messages[].fromstringSender’s number (international format without +)
messages[].idstringUnique message ID (wamid.*)
messages[].timestampstringUnix timestamp in seconds
messages[].typestringtext, image, audio, video, document, location, interactive, button
statuses[].statusstringsent, delivered, read, failed
statuses[].pricing.categorystringConversation billing category

Next steps

Platform webhooks

Understand Timely.ai’s normalized outbound webhooks.

Channels — API Reference

Manage WhatsApp channels via API.