Channels & Embeds
WhatsApp onboarding (resellers)
Enable WhatsApp calling on a tenant's phone number through Meta's Embedded Signup.
WhatsApp onboarding lets a reseller enable WhatsApp calling on one of a tenant's phone numbers. You start a session, send the end customer to a single-use signup URL where they authenticate with Meta and share their WhatsApp Business Account (WABA), and everything after that — attaching the number, capturing the registration code, enabling calling — happens on OnCore's side. The customer never sees a phone-number or OTP screen.
Authenticate with your tenant API key, or an organization key plus X-Tenant-Id — see Authentication.
Start a session#
POST /whatsapp/onboarding-sessions requires an Idempotency-Key header:
curl -X POST https://core-api.heysadie.ai/whatsapp/onboarding-sessions \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "Idempotency-Key: 8c8f3f1e-YOUR-UNIQUE-KEY" \
-H "Content-Type: application/json" \
-d '{
"tenantId": "TENANT_ID",
"phoneNumber": "+15555550123",
"displayName": "Acme Support"
}'| Body field | Required | Description |
|---|---|---|
tenantId | yes | Tenant (end-business) the number belongs to; must match the caller's API-key tenant |
phoneNumber | yes | E.164 number owned by the tenant, not already WhatsApp-enabled |
displayName | no | WhatsApp display name to request for the number (Meta reviews it). Defaults to the tenant name |
The response contains a sessionId, a single-use, tenant-bound, 15-minute signupUrl (a OnCore-hosted page wrapping Meta's Embedded Signup) with its expiresAt, and a single-use signup token (also embedded in signupUrl) for resellers hosting their own signup page. Send the end customer to signupUrl.
Idempotency
Re-sending the same Idempotency-Key for the same tenant returns the existing session (same sessionId), not a new one — even for two concurrent requests. A second in-progress onboarding for the same number under a different key is rejected with 409 WHATSAPP_ONBOARDING_IN_PROGRESS.
The Meta consent popup shows the platform provider's registered name, not your customer's business name — Meta does not white-label Tech Providers.
The state machine#
Track progress by polling GET /whatsapp/onboarding-sessions/{id} or by subscribing to the WhatsApp webhook events.
pending → authorized → verifying → [tier_pending] → completed → livewith terminal states failed, name_rejected, and abandoned.
| Status | Meaning |
|---|---|
pending | Session created; waiting for the customer to complete Embedded Signup. Past its 15-minute TTL it is reported as abandoned |
authorized | The customer authorized OnCore; the number is being attached and registered |
verifying | A verification call is placing/reading the registration code |
tier_pending | Number verified and registered, but below Meta's calling messaging-limit tier; parked until the limit is raised |
completed | Calling enabled on the number |
live | Display name approved and calling is live end-to-end |
failed / name_rejected / abandoned | Terminal — see failureReason |
On failure states the status response also carries failureReason, blame (customer = the account owner must act, resolve with your customer; system = OnCore/Meta-side, hand to OnCore), and userMessage — human-readable copy safe to show the end customer. When failureReason is otp_request_limit, retryAfterHours gives Meta's lockout window.
A stranded session self-heals to failed past its TTL: failureReason: "stalled" if it crashed in authorized (no verification call placed), or "otp_call_timeout" if it was stuck in verifying. While verifying, if the verification call was placed more than ~5 minutes ago the response carries verificationCallOverdue: true — a soft signal to prompt the customer to resend or cancel.
Resend the verification call#
POST /whatsapp/onboarding-sessions/{id}/resend-code re-triggers Meta's automated voice call that reads the registration code, for a session in verifying. The session stays verifying; otpAttempts in the response reflects the per-session budget consumed so far.
| Error | Meaning |
|---|---|
409 WHATSAPP_RESEND_NOT_ALLOWED | The session is not verifying, or the number is not yet attached |
429 WHATSAPP_RESEND_TOO_SOON | A call was requested within the last 60 seconds; details.retryAfterSeconds gives the cooldown. No attempt budget consumed |
409 WHATSAPP_OTP_ATTEMPTS_EXHAUSTED | The per-session budget is spent (the session stays verifying, not failed) |
429 WHATSAPP_OTP_REQUEST_LIMIT | Meta's rate limit; details.retryAfterHours gives the cooldown |
502 WHATSAPP_RESEND_FAILED | Non-rate-limit Meta/network failure — retryable |
404 | Cross-tenant / unknown id (anti-enumeration) |
Cancel / reset#
DELETE /whatsapp/onboarding-sessions/{id} is the self-service recovery path:
pending/authorized/verifying— cancellable: the number's WhatsApp link is cleared and the session becomesabandoned(a subsequentGETreportsfailureReason: "cancelled"). The number can immediately be re-onboarded.failed/abandoned/name_rejected— already terminal: idempotent no-op, returns the current status (200).tier_pending/completed/live— a verified/registered or live number is not cancelled here →409 WHATSAPP_ONBOARDING_NOT_CANCELLABLE.
Cancelling makes no Meta call (a re-onboard idempotently re-attaches the number) and fires no webhook — you initiated it. Cross-tenant / unknown ids return 404.
Failure reason codes#
On terminal failure, failureReason (and data.reason on the whatsapp.onboarding.failed webhook) is one of:
| Code | Meaning |
|---|---|
attach_failed / attach_failed:<metaCode> | Attaching the number to the WABA failed (e.g. attach_failed:133016) |
otp_request_limit | Meta registration rate limit — see retryAfterHours |
waba_mismatch | The authorization token does not grant the claimed WABA |
voice_routing_missing | The number has no inbound-voice routing to receive the verification call |
token_revoked | The customer revoked OnCore's access / the WABA was disabled |
name_rejected | Meta rejected the requested display name |
authorization_failed | Token exchange / post-authorization error |
register_failed | Cloud API registration failed permanently |
request_code_failed | Requesting the verification code failed (non-rate-limit) |
otp_failed | Verification failed / attempt budget exhausted |
invalid_phone_number | The stored number is not valid E.164 |
stalled | Crashed in authorized before any verification call; swept to failed past its TTL |
otp_call_timeout | Stuck in verifying past its TTL — the call was placed but never completed |
whatsapp_waba_owned_by_another_tenant | The claimed WABA is already owned by a different tenant |
Webhook events#
Onboarding progress is also pushed to your server as signed webhooks: whatsapp.onboarding.tier_pending, whatsapp.onboarding.completed, whatsapp.onboarding.failed, and whatsapp.calling.live. See WhatsApp events for the payload, signature verification, and retry semantics, and the API reference for the full endpoint specification.
New to this? WhatsApp explains what it does and when to use it, without the API detail.