Dualhook Platform · Multi-tenant WhatsApp Onboarding API
Embed WhatsApp Business onboarding into your SaaS
The Dualhook Platform API is a multi-tenant WhatsApp onboarding API for SaaS platforms. Your customers connect their own WhatsApp Business number through a co-branded Embedded Signup flow, while Dualhook handles Meta setup, Coexistence, Webhook Override, tenant mapping, and HMAC-signed lifecycle events — without proxying or storing message content.
Your end-users see your brand throughout. The brief dualhook.com/onboard handoff lasts a few seconds because Meta requires Embedded Signup to launch from a domain whitelisted on the Meta App.
Platform tier
€99 EUR · billed monthly
- 25 connections included
- +$4.50 / +€4 per extra connection
- 30-day free trial
- No per-message fees
The onboarding flow
What happens when a tenant clicks “Connect WhatsApp” in your app.
- 1
Your backend creates an onboarding session
POST /api/v1/onboarding/sessions with your tenantId, your tenant's webhookOverrideUrl + verifyToken, and your success/failure/cancel redirect URLs.
- 2
Dualhook returns a one-time onboarding URL
Valid for 1 hour. You render it inside your app, typically behind a 'Connect WhatsApp' button.
- 3
Your end-user clicks Connect WhatsApp
They land briefly on dualhook.com/onboard/<token> — co-branded with your logo and display name — and Meta's Embedded Signup popup launches.
- 4
Meta returns the OAuth code
Dualhook exchanges it, subscribes the Meta webhook to your tenant's URL, and creates the connection record.
- 5
Redirect back to your app
End-user lands on your successRedirectUrl?sessionId=…&status=completed.
- 6
A signed event webhook arrives at your event endpoint
onboarding.completed payload with wabaId, phoneNumberId, tenantId, and your round-tripped metadata, signed with HMAC-SHA256.
- 7
(Optional) Reveal the access token on demand
POST /api/v1/connections/<id>/reveal-secrets returns the Cloud API token with Cache-Control: no-store and writes an audit row keyed to your API key.
What Dualhook handles vs what stays under your control
Dualhook handles
- Meta App ownership and ongoing App Review
- Embedded Signup launcher (whitelisted domain, popup, callback)
- OAuth code → access-token exchange
- Webhook Override subscription per WABA, with sibling-aware fan-out on update/delete
- Coexistence mode for tenants keeping their WhatsApp Business mobile app
- HMAC-SHA256 lifecycle event delivery with durable retry
- Audit-logged access-token reveal endpoint and per-key rate limits
- Forward compatibility as Meta's Cloud API evolves (BSUIDs, deprecations, etc.)
Stays under your control
- Owning your tenants and the per-tenant webhook URL + verify token
- Receiving message-path webhooks directly from Meta at your endpoint
- Validating inbound message webhooks by WhatsApp envelope shape, WABA ID, and phone_number_id
- Verifying X-Dualhook-Signature on the lifecycle event webhooks
- Calling the Cloud API for outbound sends, templates, etc.
- Holding the access token only when you reveal it (no caching needed)
Who uses Platform
If your SaaS lets multiple end-customers each connect their own WhatsApp number, this is the API you want.
Vertical SaaS
Healthcare, real-estate, hospitality, legal-tech, clinics — onboarding many small-business clients, each with their own WABA, typically via Coexistence.
B2B2C CRMs & shared inboxes
Route messages to per-tenant backends without putting a BSP in the message path, and skip the auto-mark-as-read behaviour proxy providers force on you.
Internal platforms
Larger orgs that need to provision business numbers programmatically as part of their tenant lifecycle, with a clean audit trail.
Agencies graduating off spreadsheets
Agencies past 20+ client numbers who want a self-serve client onboarding link instead of doing Meta setup on a Zoom call.
Developer quickstart
One POST creates a co-branded onboarding URL for a tenant.
const r = await fetch("https://dualhook.com/api/v1/onboarding/sessions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.DUALHOOK_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
tenantId: customer.id,
tenantName: customer.name,
successRedirectUrl: `https://app.example.com/done?cust=${customer.id}`,
failureRedirectUrl: `https://app.example.com/error?cust=${customer.id}`,
cancelRedirectUrl: `https://app.example.com/cancel?cust=${customer.id}`,
webhookOverrideUrl: "https://api.example.com/whatsapp",
webhookVerifyToken: process.env.MY_WEBHOOK_VERIFY_TOKEN,
}),
});
const { onboardingUrl } = await r.json();
return redirect(onboardingUrl);When the tenant finishes, successRedirectUrl receives ?sessionId=…&status=completed and your event endpoint receives a signed onboarding.completed payload. Then reveal the access token on demand.
Why not build it yourself?
You can. Here's what your team would own indefinitely if you do.
| Area | Build yourself | Dualhook Platform |
|---|---|---|
| Meta App setup & permissions | You create and maintain your own Meta Business App, request whatsapp_business_management and whatsapp_business_messaging permissions, complete App Review. | Dualhook ships its own reviewed Meta App. Your tenants connect through it without you ever owning a Meta App. |
| Embedded Signup flow | You implement Meta's Embedded Signup JS SDK, handle Facebook Login state, message events from the popup, and the OAuth code callback. | One co-branded onboarding URL per tenant. Dualhook hosts the launcher page and handles the Embedded Signup popup end-to-end. |
| OAuth code exchange | You exchange the short-lived code for a permanent system user access token via Graph API and store it securely. | Dualhook exchanges and stores the encrypted token. You fetch it on demand via the audit-logged reveal endpoint. |
| Webhook Override configuration | You call POST /{wabaId}/subscribed_apps with override_callback_uri + verify_token. You debug verify-token mismatches and HTTPS handshake failures. | Dualhook configures Webhook Override for every connected WABA so message-path webhooks route Meta → your tenant's endpoint directly. |
| Multi-tenant mapping | You build your own table mapping waba_id and phone_number_id to your tenant id, plus the lookup logic for inbound webhooks. | Tenants are first-class. Pass your tenantId on session creation; every event is round-tripped with that id. |
| Sibling-WABA / shared webhook handling | Meta supports both WABA-level and phone-number-level Webhook Override. Whichever you choose, you handle conflicts when a tenant connects a second number under an already-connected WABA. | Dualhook Platform v1 uses WABA-level Webhook Override; sibling WABAs are detected and a clear waba_webhook_conflict error returns with the existing URL on file. PATCH fans out across siblings safely. |
| Lifecycle event delivery | You build durable, retried, signed event delivery: onboarding.started/completed/failed, connection.mode_resolved, connection.disconnected, and heartbeat events. | HMAC-SHA256 signed events, including connection.mode_resolved when Meta finalizes coexistence vs Cloud API, with retry schedule 1m → 5m → 15m → 1h → 6h → 24h. |
| Coexistence support | You implement Coexistence-mode flags so existing WhatsApp Business mobile app users keep their app working alongside Cloud API. | Coexistence is supported as a first-class onboarding option, including the 13-day heartbeat reminder UX for tenants who use it. |
| Health & quality monitoring | You poll Meta's phone-number health endpoint, parse status (AVAILABLE / LIMITED / BLOCKED) and quality (GREEN / YELLOW / RED), and surface this to tenants. | Built-in /health endpoints and dashboard. Refresh on demand or read the latest snapshot. |
| Token reveal & audit | You build a secrets API, no-cache headers, per-key rate limits, and an audit trail of who fetched which token when. | POST /connections/:id/reveal-secrets with Cache-Control: no-store. Every reveal is recorded against your API key. |
| Error model & idempotency | You design typed error codes, idempotency-key handling, and rate-limit headers from scratch. | Stable error codes, optional Idempotency-Key with 1-hour replay, X-RateLimit-* headers on every authed response. |
| Ongoing Meta API drift | When Meta deprecates an endpoint, ships BSUIDs, or changes Embedded Signup, you fix it. | Dualhook tracks Meta's deprecation cycle and ships forward-compatible changes (e.g. BSUID transition). |
Platform FAQ
What is the Dualhook Platform API?
The Platform API is a Bearer-authenticated REST API plus a co-branded onboarding URL that lets SaaS providers offer WhatsApp Business onboarding to their own end-customers. You bring the tenants; Dualhook handles Meta's Embedded Signup, OAuth code exchange, Webhook Override subscription, tenant mapping, and signed lifecycle event delivery. End-users see your brand throughout.
How is this different from the Developer, Team, and Agency tiers?
The lower tiers are for connecting WhatsApp numbers you operate yourself. Platform is for offering WhatsApp onboarding to your own customers — multi-tenant, programmatic, co-branded. If you only ever connect one or a handful of numbers for one business, the Developer, Team, or Agency tier is enough; you don't need Platform.
Does Dualhook proxy or store the messages our customers send and receive?
No. Dualhook configures Webhook Override at the WABA level so message-path webhooks (incoming messages, statuses, etc.) route directly from Meta to the URL you set per tenant. Dualhook stores configuration and operational metadata (WABA ids, tokens, webhook URLs, template metadata) but does not act as a message inbox or message-storage layer.
Can each tenant use a different webhook endpoint?
Yes — per WABA. You set webhookOverrideUrl + webhookVerifyToken per onboarding session. Dualhook Platform v1 uses Meta's WABA-level Webhook Override subscription, so phone numbers under the same connected WABA share one webhook URL and verify token in Dualhook. (Meta also offers phone-number-level overrides; per-phone routing is on the v2 list — contact us if you need it.) If the same tenant reconnects or adds another number under an already-connected WABA, the latest webhook config wins and Dualhook fans it out to the sibling connections after Meta accepts it. If that WABA belongs to another tenant, Dualhook returns waba_webhook_conflict instead of silently rerouting their numbers.
How do we get the Cloud API access token for a connection?
POST /api/v1/connections/<id>/reveal-secrets with secretTypes: ['access_token']. The response carries Cache-Control: no-store, and every reveal is recorded in secret_reveal_events keyed to your API key id so you can audit which key fetched which token when. Rate limit: 60/hour per key.
What lifecycle events does Dualhook send?
onboarding.started (end-user opens the URL), onboarding.completed (connection created and Meta webhook subscribed), onboarding.failed (cancelled, expired, OAuth exchange failed, WABA conflict, etc.), connection.disconnected (removed via API or by Meta token revocation/account block), connection.mode_resolved (connectionMode resolved from unknown to coexistence/cloud_api — so you never poll), and the coexistence heartbeat events (due_soon, overdue, confirmed). Events are HMAC-SHA256 signed and retried 1m → 5m → 15m → 1h → 6h → 24h on non-2xx response.
Keep reading
Docs, audience pages, and practical guides for the Dualhook Platform tier.
Platform API docs
Full REST reference: onboarding sessions, connections, tenants, event webhooks, errors, rate limits, quickstart.
For SaaS platforms
How vertical SaaS, B2B2C CRMs, and shared inboxes use Dualhook to let customers self-connect WhatsApp.
For agencies
Onboard client WhatsApp numbers without doing Meta setup by hand. Per-client webhook routing included.
Embedded Signup for SaaS
Practical guide to adding WhatsApp Embedded Signup to a SaaS product, with the gotchas Meta's docs leave out.
Onboard your SaaS tenants to WhatsApp in minutes
Spin up an API key on the Platform tier and create your first co-branded onboarding session today.