What URL format does WhatsApp webhook require?
Complete WhatsApp API webhook guide — configure inbound events, verify signatures, parse payloads, async processing patterns, and connect your backend to Ominiflow and Meta Cloud API. A publicly accessible HTTPS URL that accepts POST requests and returns HTTP 200. Meta does not support plain HTTP endpoints.
Last updated: 6 July 2026
WhatsApp API webhooks are HTTP callbacks that notify your server when customers send messages, when delivery status changes, or when errors occur. They are the backbone of real-time WhatsApp integrations — connecting your CRM, e-commerce platform, or custom app to conversational events without polling.
For the full integration mental model (outbound + inbound pipes, BSP layer, security), read the WhatsApp API integration guide first. New to Meta’s developer console? Start with WhatsApp Cloud API get started. This document goes deep on webhook events, payloads, signature verification, async patterns, and production failures we've debugged at 2 AM.
Start here if you're in a hurry
- Webhooks push events from Meta → Ominiflow → your endpoint (or directly to your server)
- Key events: messages (inbound), statuses (sent/delivered/read/failed), errors
- Your endpoint must return HTTP 200 in under 5 seconds; process async for heavy logic
- Verify
X-Hub-Signature-256on every POST — HMAC-SHA256 of raw body vs app secret - Use HTTPS with valid SSL — Meta rejects HTTP endpoints
- Deduplicate on
message.id— Meta retries on non-200 responses
What is a WhatsApp API webhook?
A webhook is a POST request Meta sends to a URL you configure when something happens on your WhatsApp Business account. Instead of asking "any new messages?" every few seconds, your system receives instant notifications with JSON payloads containing message text, sender phone, media URLs, and delivery receipts.
Outbound (you send) uses REST API. Inbound (customer replies, status updates) uses webhooks. Production integrations need both. Send guide: send messages programmatically.
Webhook event types
| Event | JSON path | When it fires | Typical use |
|---|---|---|---|
| messages | entry[].changes[].value.messages[] | Customer sends text, image, button reply, location | Chatbot routing, CRM logging, auto-replies |
| statuses | entry[].changes[].value.statuses[] | sent, delivered, read, failed | Campaign analytics, retry logic, SLA tracking |
| errors | entry[].changes[].value.errors[] | API or policy errors | Alerting, template debugging |
| contacts | entry[].changes[].value.contacts[] | With inbound messages | Sender profile name, wa_id |
Setup checklist — seven steps
- Get API access — Complete WhatsApp Business API onboarding on Ominiflow.
- Create HTTPS endpoint — Public URL accepting POST (e.g.
https://api.yourcompany.com/whatsapp/webhook). - Handle GET verification — Echo
hub.challengewhenhub.verify_tokenmatches your secret. - Subscribe WABA to app —
POST /{WABA_ID}/subscribed_apps— dashboard test button skips this. - Configure webhook fields — Subscribe to
messagesandmessage_template_status_updateas needed. - Verify signatures — Validate every POST before parsing JSON.
- Test with real phone — Send message from personal WhatsApp; confirm payload hits your server.
Printable: onboarding checklist
Webhook verification (GET challenge)
Meta sends a GET request during setup:
GET /whatsapp/webhook?hub.mode=subscribe
&hub.verify_token=YOUR_VERIFY_TOKEN
&hub.challenge=CHALLENGE_STRINGYour endpoint must return hub.challenge as plain text if hub.verify_token matches. Otherwise return 403.
// Express.js example
app.get('/whatsapp/webhook', (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) {
return res.status(200).send(challenge);
}
return res.sendStatus(403);
});Signature verification (critical)
Every POST includes X-Hub-Signature-256. Compute HMAC-SHA256 of the raw request body using your app secret. Compare with constant-time equality.
const crypto = require('crypto');
function verifySignature(rawBody, signatureHeader, appSecret) {
if (!signatureHeader?.startsWith('sha256=')) return false;
const expected = crypto
.createHmac('sha256', appSecret)
.update(rawBody) // raw bytes, NOT parsed JSON
.digest('hex');
const received = signatureHeader.slice(7);
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(received, 'hex')
);
}
// Express: use express.raw() or capture rawBody before JSON parse
app.post('/whatsapp/webhook', express.raw({ type: 'application/json' }), (req, res) => {
if (!verifySignature(req.body, req.headers['x-hub-signature-256'], APP_SECRET)) {
return res.sendStatus(401);
}
// Acknowledge immediately
res.sendStatus(200);
// Queue async processing
queue.add('process-wa-webhook', JSON.parse(req.body.toString()));
});Common bug: Parsing JSON before signature verification invalidates the HMAC. Always verify against the raw body bytes Meta sent.
Sample inbound message payload
{
"object": "whatsapp_business_account",
"entry": [{
"id": "WABA_ID",
"changes": [{
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "919876543210",
"phone_number_id": "PHONE_NUMBER_ID"
},
"contacts": [{
"profile": { "name": "Priya Sharma" },
"wa_id": "919988776655"
}],
"messages": [{
"from": "919988776655",
"id": "wamid.HBgNOTE...",
"timestamp": "1720000000",
"type": "text",
"text": { "body": "Where is my order ORD-88421?" }
}]
},
"field": "messages"
}]
}]
}Button reply payload
"messages": [{
"from": "919988776655",
"id": "wamid.HBgNOTE...",
"type": "interactive",
"interactive": {
"type": "button_reply",
"button_reply": { "id": "confirm_yes", "title": "YES" }
}
}]COD flows branch on button_reply.id. Guide: COD confirmation
Sample delivery status payload
{
"entry": [{
"changes": [{
"value": {
"statuses": [{
"id": "wamid.HBgNOTE...",
"status": "delivered",
"timestamp": "1720000060",
"recipient_id": "919988776655",
"conversation": {
"id": "CONVERSATION_ID",
"origin": { "type": "utility" }
},
"pricing": { "billable": true, "category": "utility" }
}]
},
"field": "messages"
}]
}]
}Status values: sent → delivered → read (or failed with errors array). Store message.id from your send response to correlate.
Async processing pattern
Meta retries on non-200 or slow responses. Duplicate events without deduplication = duplicate CRM entries.
- Receive POST → verify signature
- Return HTTP 200 immediately (under 5 seconds, ideally under 500ms)
- Enqueue job — Redis, SQS, RabbitMQ, or database outbox
- Worker processes — parse payload, dedupe on
message.id, update CRM, trigger bot - Idempotent handlers — same
message.idprocessed twice = no-op second time
| Anti-pattern | What breaks | Fix |
|---|---|---|
| CRM API call inside webhook handler | Timeout → Meta retry → duplicate lead | Queue + dedupe |
| Shared hosting with 30s cold start | Intermittent missed events | Host on GCP/AWS with warm instances |
| No signature check | Spoofed payloads | HMAC verification on raw body |
| Logging full payloads | PII in plaintext logs | Redact phone and message body |
Integration patterns
1. CRM sync (inbound → CRM)
Customer replies → webhook → create/update CRM lead, log conversation on timeline. Native: Zoho CRM integration.
2. E-commerce order lookup
Customer sends order ID → webhook triggers lookup → chatbot responds with shipping status. Shopify guide
3. Outbound triggers (your app → WhatsApp)
Your backend POSTs to Ominiflow API on business events. Use approved message templates for business-initiated sends.
4. Chatbot API nodes
Ominiflow flow builder HTTP nodes call your APIs during conversations — complementing external webhooks for bidirectional automation.
Security best practices
- Validate
X-Hub-Signature-256on every POST — primary guard, not IP allowlists alone - Return 200 immediately; never block on downstream API calls
- Store app secret in secrets manager — not in git
- Redact PII in logs — hash phone numbers if you must log identifiers
- Rotate verify tokens and app secrets when team members leave
- HTTPS only — valid certificate chain; Meta rejects self-signed
Troubleshooting webhooks
| Symptom | Root cause | Fix |
|---|---|---|
| Dashboard test works; real messages don't arrive | WABA not subscribed to app | POST /{WABA_ID}/subscribed_apps |
| Outbound OK; inbound dead | Phone PENDING or messages not subscribed | Re-verify number; check subscription fields |
| Duplicate CRM entries | Webhook retries, no idempotency | Dedupe on message.id |
| 401 on signature check | Parsed JSON before HMAC | Verify raw body bytes |
| Events arrive hours late | Slow endpoint or cold hosting | Return 200 fast; queue processing |
More: common API mistakes
Next steps
Read: integration guide · send messages · Ominiflow pricing
FAQ
Do I need a developer to set up webhooks? expand_more
Basic no-code flows work inside Ominiflow's chatbot builder. Custom CRM or backend sync requires a developer to build and host an HTTPS endpoint.
What URL format does WhatsApp webhook require? expand_more
Publicly accessible HTTPS URL accepting POST requests, returning HTTP 200. Meta does not support plain HTTP.
How fast must my webhook respond? expand_more
Return HTTP 200 within 20 seconds (Meta's limit). Best practice: under 5 seconds. Queue heavy processing asynchronously.
How do I verify webhook signatures? expand_more
Compute HMAC-SHA256 of the raw request body with your app secret. Compare to X-Hub-Signature-256 header using constant-time equality.
Can webhooks work with Shopify or WooCommerce? expand_more
Yes — via Ominiflow native connectors or middleware like Make. Shopify integration guide
What is the difference between webhooks and REST API? expand_more
Webhooks are inbound (Meta pushes to you). REST API is outbound (you push messages to Meta). Production integrations use both.
Why do I get duplicate webhook events? expand_more
Meta retries on non-200 responses or timeouts. Deduplicate on message.id in your database before processing.
Can Ominiflow forward webhooks to my server? expand_more
Yes — configure primary webhooks in the dashboard. Ominiflow receives from Meta and forwards normalized payloads to your endpoint. Last updated July 2026. Webhook integration guide for developers — verified against Meta Cloud API documentation.