Skip to main content

Webhook Verification

Meta requires webhook verification during setup. Your endpoint must respond to GET requests with the challenge parameter.
curl -X GET "https://your-api.com/api/whatsapp/webhook?hub.mode=subscribe&hub.verify_token=YOUR_VERIFY_TOKEN&hub.challenge=CHALLENGE_STRING"

Query Parameters

hub.mode
string
required
Must be subscribe for verification
hub.verify_token
string
required
Verification token configured in your environment (WHATSAPP_VERIFY_TOKEN)
hub.challenge
string
required
Random string from Meta that must be returned in the response

Response

Returns the challenge string with status 200 if verification succeeds, or 403 if token doesn’t match.
The verify token must match the WHATSAPP_VERIFY_TOKEN environment variable. Default value: ambiotec_whatsapp_verify_token_2026

Receive Webhook Events

Receive incoming WhatsApp messages and status updates from Meta.
curl -X POST https://your-api.com/api/whatsapp/webhook \
  -H "Content-Type: application/json" \
  -d '{
    "object": "whatsapp_business_account",
    "entry": [
      {
        "id": "WHATSAPP_BUSINESS_ACCOUNT_ID",
        "changes": [
          {
            "value": {
              "messaging_product": "whatsapp",
              "metadata": {
                "display_phone_number": "15550001234",
                "phone_number_id": "123456789"
              },
              "contacts": [
                {
                  "profile": {
                    "name": "John Doe"
                  },
                  "wa_id": "50212345678"
                }
              ],
              "messages": [
                {
                  "from": "50212345678",
                  "id": "wamid.HBgNNTA...",
                  "timestamp": "1234567890",
                  "type": "text",
                  "text": {
                    "body": "Hello, I need support"
                  }
                }
              ]
            },
            "field": "messages"
          }
        ]
      }
    ]
  }'

Webhook Payload Structure

object
string
required
Must be whatsapp_business_account
entry
array
required
Array of webhook entries
entry[].changes
array
Array of changes
changes[].field
string
Event type (e.g., messages)
changes[].value
object
Event data containing messages, contacts, and statuses

Message Types Supported

text
object
Text messages with body field
image
object
Images with id, mime_type, and optional caption
document
object
Documents with id, mime_type, filename, and optional caption
audio
object
Audio files with id and mime_type
video
object
Videos with id, mime_type, and optional caption
location
object
Location data with latitude, longitude, name, and address

Status Update Payload

{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "changes": [
        {
          "value": {
            "statuses": [
              {
                "id": "wamid.HBgNNTA...",
                "status": "delivered",
                "timestamp": "1234567890",
                "recipient_id": "50212345678"
              }
            ]
          },
          "field": "messages"
        }
      ]
    }
  ]
}

Status Values

  • sent - Message sent to Meta servers
  • delivered - Message delivered to recipient’s device
  • read - Message read by recipient
  • failed - Message failed to send

Response

Always returns 200 OK to acknowledge receipt. Processing happens asynchronously.
Important: You must respond with 200 OK quickly (within 20 seconds) or Meta will retry the webhook delivery. All processing is done asynchronously after responding.

Webhook Processing

The webhook automatically:
  1. Logs all incoming webhooks to whatsapp_webhook_logs table
  2. Creates or updates conversations for new contacts
  3. Saves messages to the database
  4. Updates message delivery statuses
  5. Notifies assigned users of new messages

Security Considerations

This endpoint does NOT verify webhook signatures. In production, implement signature verification using the X-Hub-Signature-256 header:
const crypto = require('crypto');
const signature = req.headers['x-hub-signature-256'];
const hash = crypto
  .createHmac('sha256', process.env.WHATSAPP_APP_SECRET)
  .update(JSON.stringify(req.body))
  .digest('hex');

if (`sha256=${hash}` !== signature) {
  return res.sendStatus(403);
}

Build docs developers (and LLMs) love