Home › Guides › Receiving WhatsApp messages at a webhook: payloads, signatur

Receiving WhatsApp messages at a webhook: payloads, signatures, retries and the 24-hour rule

By Qilin Software Lab Private Limited · Published 2026-10-14 · 3 min read

Everything a WhatsApp Business Platform number receives arrives through a webhook: inbound messages, delivery and read statuses, template status changes, account alerts. Getting the endpoint right is most of the integration.

What Meta sends

Meta posts JSON to one URL per app. Each post has an entry list; each entry has changes; each change has a field (usually messages) and a value holding the phone number id the event belongs to, optional contacts, and either messages (inbound) or statuses (sent, delivered, read, failed). One post can carry several messages, and the same message can be posted more than once.

Verifying it is really Meta

Every post carries X-Hub-Signature-256: sha256= followed by an HMAC-SHA256 of the raw body using the app secret. Compute it over the raw bytes (not the re-serialised JSON) and compare with a constant-time function. Reject anything that fails. On first setup Meta also sends a GET with hub.verify_token and hub.challenge; echo the challenge only if the token matches.

Idempotency

Store the message id (wamid…) with a unique constraint and ignore repeats. Statuses can arrive out of order (a read before a delivered), so apply them as a maximum, not a sequence.

Respond fast, work later

Meta expects a 200 within a few seconds. Write to a queue or a database and return; do the CRM lookup, the AI call, the Tally update afterwards. Slow endpoints get retried, and persistently slow ones get the app's webhook disabled.

What a connector adds

With a multi-tenant connector like NumberBridge there is one Meta webhook for all customers. The connector verifies Meta's signature, finds the tenant by phone number id, stores the message, and forwards a cleaner event to the tenant's own URL:

POST https://yourapp.com/hooks/whatsapp
X-NumberBridge-Signature: <hex HMAC-SHA256 of the body with your secret>
X-NumberBridge-Event: message

{"event":"message",
 "connection":{"phone_number_id":"1493…","display_phone":"+91 95917 24716"},
 "contact":{"wa_id":"919716019716","profile":{"name":"Mahavir Traders"}},
 "message":{"id":"wamid.HBg…","from":"919716019716","timestamp":1760000000,"type":"text","text":{"body":"ledger bhej do"}}}

The forward is retried three times with backoff and every attempt is written to a delivery log you can read, so a bad deploy on your side does not lose a day of messages.

The 24-hour rule, in webhook terms

Each inbound message opens or extends a 24-hour service window for that customer. Inside it you may send free-form text, media and documents. Outside it only an approved template is accepted; the API returns an error otherwise. Store the timestamp of the last inbound message per customer and your send code can choose the right path without guessing.

Minimal endpoint

from fastapi import FastAPI, Request, Header
import hmac, hashlib

app = FastAPI()
SECRET = "your webhook secret"

@app.post("/hooks/whatsapp")
async def hook(request: Request, x_numberbridge_signature: str = Header("")):
    raw = await request.body()
    good = hmac.compare_digest(hmac.new(SECRET.encode(), raw, hashlib.sha256).hexdigest(), x_numberbridge_signature)
    if not good:
        return {"ok": False}
    event = await request.json()
    queue.put(event)      # handle later
    return {"ok": True}

Put software on your business number without losing the phone

NumberBridge connects an existing WhatsApp Business app number to the Cloud API in two minutes. Flat monthly fee, no per-message markup, 14-day trial.

Start free Pricing

Related guides