Skip to main content

Messaging-provider webhooks

Why these are different

Twilio and Meta sign their webhooks (HMAC), so the platform verifies a signature. MSG91 and Gupshup do not HMAC-sign their callbacks. The accepted mechanism for those is a secret token appended to the callback URL (plus an optional edge IP allow-list). Every handler fails closed — a missing or incorrect token, or an unset secret, returns 403 before any state mutation — so a spoofed callback can never forge an opt-out or a delivery status.

Endpoints

ProviderChannelEndpointAuth
MSG91SMS (India / +91)/api/msg91-webhook?token= (or x-msg91-token header)
GupshupWhatsApp/api/gupshup-webhook?token=
TwilioSMS/api/sms-webhookTwilio signature
MetaWhatsApp/webhooks/whatsappX-Hub-Signature-256

MSG91 — /api/msg91-webhook

Register this URL in the MSG91 dashboard as the delivery-report (DLR) webhook and, if two-way SMS is enabled, the incoming-message webhook — with the shared secret appended:

https://<app>/api/msg91-webhook?token=<MSG91_WEBHOOK_SECRET>

Two event families, both normalized by the MSG91 adapter and fed into the same provider-neutral handlers Twilio uses:

  • Delivery report (DLR)applyDeliveryStatus (monotonic status write).
  • Inbound messagehandleInboundKeyword (STOP/START opt-out, parity with the rest of SMS compliance).

Gupshup — /api/gupshup-webhook

Register this URL in the Gupshup app's callback settings, with the secret token:

https://<app>/api/gupshup-webhook?token=<GUPSHUP_WEBHOOK_SECRET>

Two event families, normalized by the Gupshup adapter into the same provider-neutral handlers Meta uses:

  • message-event (DLR)applyWhatsAppDeliveryStatus (monotonic status write).
  • message (inbound) → STOP-keyword opt-out (parity with SMS).
Gupshup inbound scope

This handler covers delivery reconciliation and the compliance-critical opt-out path. Full inbound conversation ingestion for Gupshup (session window, click-to-WhatsApp, agent inbox) is a follow-up; today, two-way conversations run on Meta's Cloud API.

The monotonic status pipeline

Providers emit status callbacks that can arrive duplicated and out of order — a sent DLR landing after delivered, a retried callback re-delivering queued. Writing whatever arrives last would corrupt the message log (a delivered message flipping back to "sent", a read receipt clobbered).

Every DLR — from any provider — passes through one shared guard that makes status writes monotonic: it only advances along the lifecycle and makes terminal states sticky, so late or duplicate callbacks are safe no-ops.

queued → sent → delivered → read (positive lifecycle; higher = further)
failed / undelivered (terminal — sticky)

The status vocabulary is provider-neutral and matches the strings already stored on the message log, so nothing downstream changes when a message was sent via MSG91 vs. Twilio, or Gupshup vs. Meta.

Configuration checklist

  1. Set the secret env var(s): MSG91_WEBHOOK_SECRET, GUPSHUP_WEBHOOK_SECRET.
  2. Register the callback URL(s) with the token appended, in each provider's dashboard.
  3. (Optional) Restrict the endpoint to the provider's published egress IPs at the edge.
  4. Verify with a test send — the message log should progress queued → sent → delivered and a STOP reply should record an opt-out.

See also