Platform API: Onboarding sessions

Create and look up co-branded onboarding sessions. Idempotency, redirect URL whitelisting, expiry, and the 1-hour session window.

A Dualhook onboarding session is a one-time co-branded WhatsApp Embedded Signup link for a single tenant (valid for 1 hour by default, configurable up to 24h). Your backend creates the session, your tenant clicks the resulting URL inside your app, and Dualhook posts a signed lifecycle event to your endpoint when onboarding completes. Each session round-trips your tenant id and free-form metadata.

What an onboarding session is

A session represents a single attempt by a single tenant to connect a WhatsApp Business number through your product. Sessions are scoped to your API key (Platform-tier subscription) and live for 1 hour after creation by default (override with expiresInSeconds, 300–86400). After expiry, the URL stops working and onboarding.failed fires with reason='expired'.

Create a session

POST https://dualhook.com/api/v1/onboarding/sessions
Authorization: Bearer dh_live_xxx
Content-Type: application/json
Idempotency-Key: <optional, recommended>

{
  "tenantId": "cust_abc123",
  "tenantName": "Acme Clinic",
  "successRedirectUrl": "https://app.example.com/connected",
  "failureRedirectUrl": "https://app.example.com/error",
  "cancelRedirectUrl": "https://app.example.com/cancel",
  "webhookOverrideUrl": "https://api.example.com/wh",
  "webhookVerifyToken": "<your endpoint's hub.verify_token>",
  "metadata": { "internal_id": "..." }
}

Returns:

{
  "sessionId": "sess_xxxxxxxx",
  "onboardingUrl": "https://dualhook.com/onboard/<token>",
  "expiresAt": "2026-04-29T13:34:56Z",
  "idempotencyExpiresAt": "2026-04-29T13:34:56Z"
}
  • All redirect URLs must match a pattern in your Allowed Redirect URLs whitelist (configurable in the dashboard tab) — this prevents open-redirect abuse if your API key is ever stolen.
  • metadata is round-tripped on every onboarding event for that session.

Webhook verification preflight

Before creating the session, Dualhook sends a simulated Meta verification GET to your webhookOverrideUrl?hub.mode=subscribe&hub.verify_token=<your token>&hub.challenge=<random> — and requires HTTP 200 with the body echoing the challenge exactly. If your endpoint would fail Meta's real verification (403 from a WAF, 404, POST-only route, >5s response, JSON-wrapped challenge, …), session creation fails immediately with webhook_preflight_failed and a preflight.category telling you exactly what to fix — instead of your tenant completing the entire Embedded Signup flow and then hitting onboarding.failed.

Endpoint requirements (same as Meta's): a GET handler on the webhook path, unauthenticated, responding in under 5 seconds with the raw hub.challenge value as plain text — no WAF challenge pages, no JSON wrapper, no heavy work before responding.

To skip the preflight, pass "skipWebhookPreflight": true in the body. You need this if your endpoint allowlists Meta's IP ranges or user-agent: the preflight originates from Dualhook's infrastructure, not Meta's, so such an endpoint fails preflight while working fine in production.

Look up a session

GET /api/v1/onboarding/sessions/:id

Returns status, redirect URLs, and the resulting connectionId if completed.

Webhook configuration is WABA-scoped

Meta supports both WABA-level and phone-number-level Webhook Override (POST /{wabaId}/subscribed_apps and POST /{phone-number-id}/subscribed_apps). Dualhook Platform v1 uses the WABA-level path only — one override_callback_uri and verify_token per connected WABA. As a consequence, all phone numbers under one connected WABA share one webhook URL and verify token in Dualhook.

Re-onboarding the same tenant: latest config wins. When a session's tenant already has connections on the WABA being onboarded, the new webhookOverrideUrl + webhookVerifyToken simply replace the old ones — for every connection on that WABA. This makes retries, token rotation, and test→prod endpoint migrations work without any cleanup:

  • Same phone, identical webhook config — the onboarding completes idempotently against the existing connection (no duplicate row, no error). The onboarding.completed event carries alreadyConnected: true.
  • Same phone, changed webhook config — the existing connection is reconnected in place with the new config and a fresh access token. The event carries webhookConfigReplaced: true.
  • New phone on an already-connected WABA, changed webhook config — the new connection is created and every sibling connection on the WABA adopts the new config once Meta accepts it. The event carries webhookConfigReplaced: true and updatedSiblingConnectionIds.

Across tenants the conflict stays strict. If the WABA already has connections belonging to a different tenant of yours, the new config must exactly match the existing webhookOverrideUrl + webhookVerifyToken, or the session fails with waba_webhook_conflict — replacing a WABA-level override would silently reroute the other tenant's numbers. The error tells you whether the URL, the verify token, or both mismatched, and includes the existing URL on file. To deliberately change a WABA's webhook config outside onboarding, use PATCH /api/v1/wabas/:wabaId/webhook.

In practice the WABA-shared model 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 webhook routing, contact us — moving to per-phone overrides is on the v2 list.

Session expiry and idempotency

  • Sessions are valid for 1 hour by default. Pass expiresInSeconds (300–86400) to override — useful when you email or queue the onboarding URL instead of opening it immediately.
  • Idempotency-Key is optional but recommended; reusing it within 1 hour returns the same session, allowing safe retries. Different body with the same key returns idempotency_key_collision.

Related

  • Platform API (Multi-Tenant Onboarding)Build embedded WhatsApp onboarding into your SaaS. Programmatic connection creation, per-tenant webhook routing, HMAC-signed event webhooks.
  • Platform API: Quickstart10-line code sample to create your first onboarding session and redirect a tenant to the co-branded Embedded Signup flow.
  • Platform API: Event webhooksReceive HMAC-SHA256 signed lifecycle events: onboarding, connection.mode_resolved, disconnect, and coexistence heartbeat events.
  • Platform API: ErrorsStable error codes returned by the Dualhook Platform API and what each one means for your integration.
Browse more docsStart Free Trial