Guide

Webhook Override for multi-tenant WhatsApp SaaS products

Webhook Override is Meta's mechanism for routing supported WhatsApp webhook fields to an alternate callback URL instead of the default app-level webhook. In a multi-tenant SaaS architecture, that is exactly what you want: customer-path traffic for each tenant lands on whichever endpoint that tenant owns, not on a shared inbox or proxy. Here's how Dualhook Platform v1 applies that model and the WABA-level constraint that comes with it.

The architecture in one paragraph

Meta sends override-capable webhook fields directly to the URL configured for each WABA or phone number via Webhook Override. Dualhook applies the WABA-level alternate callback during onboarding using your tenant's webhook endpoint and verify token. Dualhook handles non-overridable management events (template approvals, account alerts, account updates) that keep dashboards and lifecycle events in sync, but stays out of the customer message path entirely.

Per-tenant webhook routing

When you create a Dualhook onboarding session for a tenant, you pass the tenant's webhook URL and verify token on the request. After the tenant completes Embedded Signup, Dualhook applies those values as the WABA-level alternate callback. From that point forward, Meta delivers supported webhook fields for that WABA to the tenant URL instead of Dualhook's app-level callback.

Meta then sends a GET verification challenge to your tenant's endpoint with hub.mode=subscribe, hub.verify_token, and hub.challenge. Your endpoint must compare the verify_token and return the raw hub.challenge as plain text with HTTP 200.

The WABA-level constraint in Dualhook v1

Meta supports both WABA-level and phone-number-level alternate callbacks. Dualhook Platform v1 uses the WABA-level path. That's a Dualhook implementation choice, not a Meta limit, but it means in Dualhook today all phone numbers under one connected WABA share one webhook URL and verify token.

When the same tenant re-onboards onto an already-connected WABA with different webhook values, the new config wins: Dualhook re-subscribes Meta with it and updates every sibling connection on the WABA, so retries, verify-token rotation, and test-to-prod endpoint moves need no cleanup. Only when the WABA already carries another tenant's connections does a mismatch return waba_webhook_conflict — explicit failure beats silently rerouting someone else's numbers.

In practice this is what most partners want: one endpoint per business, demux by phone_number_id from the payload (which you'd do anyway with Cloud API). If you need per-phone-number routing in Dualhook, contact us — it's on the v2 list.

Signature verification

Lifecycle events from Dualhook (onboarding.started/completed/failed, connection.mode_resolved, connection.disconnected, and heartbeat events) come with X-Dualhook-Signature — an HMAC-SHA256 of the raw request body using your Dualhook-issued partner signing secret. Capture and verify the raw bytes before your framework re-parses them, or the HMAC mismatches.

Inbound message-path webhooks from Meta carry X-Hub-Signature-256, but in the Embedded Signup flow it's signed by Dualhook's Meta app and not customer-verifiable. Validate the envelope shape, WABA ID, and phone_number_id per tenant instead, and keep the override URL high-entropy. This is separate from outbound Graph API POSTs, which use the WhatsApp access token for bearer authentication.

Updating webhook URLs (sibling-WABA fan-out)

When a tenant changes their endpoint, PATCH /api/v1/connections/<id> with the new webhookUrl + webhookVerifyToken. Because Dualhook v1 uses one WABA-level subscription per WABA, the change fans out to every sibling connection under the same WABA and returns affectedConnectionIds. Dualhook calls Meta first; if Meta rejects the new endpoint we return webhook_subscribe_failed and leave your DB rows untouched.

Why direct Meta routing matters in a SaaS context

Most WhatsApp BSPs sit in the message path. That means inbound messages travel Meta → BSP → tenant, the BSP becomes a message-storage layer (often with retention defaults and auto-mark-as-read behaviour), and your tenants inherit a compliance surface area they didn't ask for. With Webhook Override configured by Dualhook, message-path traffic goes Meta → tenant directly — Dualhook does not proxy or store the messages.

Ready to skip the Meta setup?

Spin up an API key on the Platform tier and create your first onboarding session in minutes.