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.