Skip to content
WhatsApp

POSThttps://core-api.heysadie.ai/whatsapp/onboarding-sessions

tenantId phoneNumber displayName success data sessionId token signupUrl expiresAt metadata timestamp version path error code message details field

Start onboarding session

Start WhatsApp calling onboarding for one of the tenant's phone numbers. Returns a **single-use, tenant-bound, 15-minute** `signupUrl` (a Sadie-hosted page wrapping Meta's Embedded Signup). Send the end customer to that URL; they authenticate with Meta and share their WhatsApp Business Account (WABA). Everything after that (attaching the number, capturing the registration code, enabling calling) happens server-side. **The customer never sees a phone-number or OTP screen.** ### Idempotency Pass an `Idempotency-Key` header. Re-sending the same key for the same tenant returns the existing session (same `sessionId`), not a new one. Two concurrent requests with the same key resolve to the one session (no duplicate). A second **in-progress** onboarding for the same number (different key) is rejected with `409 WHATSAPP_ONBOARDING_IN_PROGRESS`. ### Progress Poll `GET /whatsapp/onboarding-sessions/{id}` or subscribe to the reseller webhooks (`whatsapp.onboarding.tier_pending | completed | failed`, `whatsapp.calling.live`). The Meta consent popup shows **"Sadie"**: Meta does not white-label Tech Providers. ## Reseller webhooks (onboarding lifecycle) Sadie POSTs onboarding lifecycle events to the reseller's configured server URL (`tenants.server_url`) as they happen. Configure that URL to receive them; this is the outbound contract you build a receiver for. ### Event types - `whatsapp.onboarding.tier_pending`: number verified & registered but below Meta's calling messaging-limit tier; parked until the portfolio limit is raised. - `whatsapp.onboarding.completed`: calling enabled on the number. - `whatsapp.onboarding.failed`: terminal failure; branch on `data.reason`. - `whatsapp.calling.live`: display name approved and calling is live end-to-end. ### Payload (JSON body) ```json { "event": "whatsapp.onboarding.completed", "sessionId": "0194cc1a-eea6-7351-903d-9dcf479e14ae", "tenantId": "0194cc1a-eea6-7351-903d-9dcf479e14ae", "phoneNumber": "+15555550123", "timestamp": "2026-07-15T18:00:00.000Z", "data": { "messagingLimitTier": "TIER_2K" } } ``` `data` is event-specific and open (new keys may be added). On `failed`, `data.reason` is set; `otp_request_limit` also carries `data.retryAfterHours`. ### Signature (verify EVERY delivery) Each POST carries two headers: - `X-Sadie-Signature: sha256=<hex>`: HMAC-SHA256 of the **raw request body bytes** (verify BEFORE JSON-parsing), keyed by your per-tenant shared secret (the same `client-server-secret` used for end-of-call reports). - `X-Sadie-Delivery: <uuid>`: the delivery id. Verify by recomputing `"sha256=" + hex(hmacSHA256(secret, rawBody))` and comparing with a constant-time equality check. Reject on mismatch. ### Delivery & replay-idempotency Delivery is at-least-once: a non-2xx or timeout is retried up to 5× with exponential backoff (~10s per-attempt timeout). **All retries of one event reuse the same `X-Sadie-Delivery` id**: dedupe on it (treat it as the idempotency key) so a retried delivery isn't processed twice. Respond `2xx` promptly to stop retries. Events may also arrive out of order; treat `timestamp` as the ordering hint and the session `status` as authoritative. ### `data.reason` failure codes (on `whatsapp.onboarding.failed`) - `attach_failed` / `attach_failed:<metaCode>` (e.g. `attach_failed:133016`) - `otp_request_limit` (with `data.retryAfterHours`) - `waba_mismatch` - `voice_routing_missing` - `token_revoked` - `name_rejected` - `authorization_failed` - `register_failed` - `request_code_failed` - `otp_failed` - `invalid_phone_number` - `stalled` (crashed in `authorized`, before any verification call) - `otp_call_timeout` (stuck in `verifying` past its TTL: the call was placed but never completed) - `whatsapp_waba_owned_by_another_tenant`

Try it in the API Explorer

Example request

Terminal
curl -X POST "https://core-api.heysadie.ai/whatsapp/onboarding-sessions" \
  -H "Authorization: ApiKey YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tenantId": "0195f1e2-0000-7000-8000-000000000000",
    "phoneNumber": "string"
  }'

Authentication

Authenticate with an API key in the Authorization header using the ApiKey scheme. See Authentication for how keys work and where to get them.

Authorizationstringrequired

API key in the ApiKey scheme: ApiKey YOUR_API_KEY. Tenant keys authorize their own tenant; organization keys can act on any tenant in the organization.

X-Tenant-Idstring (uuid)optional

Required when authenticating with an organization key: it selects the tenant the request acts on. Omit with tenant keys.

Request

Body application/json · required

tenantIdstring (uuid)required

Tenant (end-business) the number belongs to. Must match the caller's API-key tenant.

phoneNumberstringrequired

E.164 number owned by the tenant, not already WhatsApp-enabled.

displayNamestringoptional

Optional WhatsApp display name to request for the number (Meta reviews it). Defaults to the tenant name.

Responses

200
successbooleanrequired

true on every 2xx

dataobjectoptional

The result

Show 4 child properties
sessionIdstring (uuid)required
tokenstringrequired

Single-use signup JWT (also embedded in signupUrl). Reseller-hosted signup pages use this to call /whatsapp/signup/complete after running their own Embedded Signup.

signupUrlstringrequired

Sadie-hosted Embedded Signup URL (single-use, tenant-bound, 15-min TTL). Send the end customer here, OR host your own page and use `token` instead.

expiresAtstringrequired

ISO-8601 expiry of the signup URL / session.

metadataobjectoptional

Request metadata

Show 3 child properties
timestampstringrequired

When the server answered, ISO 8601 UTC

versionstringrequired

API version

pathstringoptional

The URL that was requested

400The request is invalid. `details` lists each field that failed
successbooleanrequired

false on every error

errorobjectoptional

What went wrong: a stable `code` to branch on and a human-readable `message`

Show 3 child properties
codeenumrequired

A parameter or body field broke a rule

Allowed values:"VALIDATION_ERROR"
messagestringrequired

Human-readable explanation. Do not parse it; branch on `code`

detailsobject[]optional

One entry per field that failed

Show 2 item properties
fieldstringrequired

The offending field

messagestringrequired

The rule it broke

metadataobjectoptional

Request metadata

Show 3 child properties
timestampstringrequired

When the server answered, ISO 8601 UTC

versionstringrequired

API version

pathstringoptional

The URL that was requested

401Missing or invalid credentials
successbooleanrequired

false on every error

errorobjectoptional

What went wrong: a stable `code` to branch on and a human-readable `message`

Show 2 child properties
codeenumrequired

No valid credentials

Allowed values:"UNAUTHORIZED"
messagestringrequired

Human-readable explanation. Do not parse it; branch on `code`

metadataobjectoptional

Request metadata

Show 3 child properties
timestampstringrequired

When the server answered, ISO 8601 UTC

versionstringrequired

API version

pathstringoptional

The URL that was requested

403The credentials are valid but may not use this route
successbooleanrequired

false on every error

errorobjectoptional

What went wrong: a stable `code` to branch on and a human-readable `message`

Show 2 child properties
codeenumrequired

The credentials may not use this route

Allowed values:"FORBIDDEN"
messagestringrequired

Human-readable explanation. Do not parse it; branch on `code`

metadataobjectoptional

Request metadata

Show 3 child properties
timestampstringrequired

When the server answered, ISO 8601 UTC

versionstringrequired

API version

pathstringoptional

The URL that was requested

404Not found, or outside the caller's scope
successbooleanrequired

false on every error

errorobjectoptional

What went wrong: a stable `code` to branch on and a human-readable `message`

Show 2 child properties
codeenumrequired

An id names nothing the caller can see

Allowed values:"RESOURCE_NOT_FOUND"
messagestringrequired

Human-readable explanation. Do not parse it; branch on `code`

metadataobjectoptional

Request metadata

Show 3 child properties
timestampstringrequired

When the server answered, ISO 8601 UTC

versionstringrequired

API version

pathstringoptional

The URL that was requested

500An unexpected server error
successbooleanrequired

false on every error

errorobjectoptional

What went wrong: a stable `code` to branch on and a human-readable `message`

Show 2 child properties
codeenumrequired

An unexpected server failure

Allowed values:"INTERNAL_SERVER_ERROR"
messagestringrequired

Human-readable explanation. Do not parse it; branch on `code`

metadataobjectoptional

Request metadata

Show 3 child properties
timestampstringrequired

When the server answered, ISO 8601 UTC

versionstringrequired

API version

pathstringoptional

The URL that was requested