Messaging & channels
Your OnCore assistant answers WhatsApp voice calls and inbound WhatsApp text on a number you already have.
Customers who live in WhatsApp can call you there too. They open your business chat, tap the call button, and your assistant picks up: same assistant, same number, no app switch for them.
Overview#
WhatsApp calling adds a second way to reach the assistant on a phone number you already have with OnCore. Once the number is enabled for WhatsApp, customers can call you from inside the WhatsApp app (no dialling, just a tap in the chat) and the same assistant that answers your phone line takes the call. The number keeps answering ordinary phone calls exactly as before; WhatsApp calling sits alongside, and the call travels over the internet rather than the phone network.
- Meet customers where they already are: for many customers WhatsApp is the phone; the call button in your chat now reaches a live assistant instead of ringing out.
- Serve callers abroad without international dialling: a customer in another country taps to call over the internet instead of dialling a foreign number.
- Keep one assistant across phone and WhatsApp: both routes land on the assistant behind the same number, so its behaviour, greeting, and tasks stay in one place.
- Enable numbers at scale through the API: start an onboarding with a single request, hand the business owner a signup link, and follow progress by webhook until calling is live; the owner never handles codes or verification screens.
- Tell WhatsApp calls apart in your reporting: each call is announced to your systems with a
whatsappsource marker, alongside the same call records you get for phone calls.
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.
Once the number is onboarded, inbound WhatsApp text is a separate channel flag on the assistant. Calling and texting share the same WABA number; they do not share the same on/off switch.
In the dashboard: Sign up to WhatsApp on the assistant, then turn WhatsApp Enabled on under Messaging and save.
Authenticate with your tenant API key, or an organization key plus X-Tenant-Id. See Authentication.
Enable or disable the WhatsApp text channel#
Toggle two-way WhatsApp text per assistant. OnCore only answers customer-initiated text. It does not send templates, start a conversation, or handle media.
A WhatsApp text conversation behaves like an SMS conversation: the same opening line, the same ways for a thread to end, and the same handling of a text sent right after a goodbye.
curl -X PATCH https://core-api.heysadie.ai/assistants/ASSISTANT_ID/whatsapp-enabled \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "whatsappEnabled": true }'The response returns the assistant id and its updated featureFlags. When WhatsApp is disabled, OnCore ignores inbound WhatsApp text for the assistant's number(s). Other channel flags (SMS, for example) are left alone.
Enabling requires an onboarded number
Enabling requires a linked phone number that has already been onboarded to WhatsApp (a Meta phone-number ID on the number). If it doesn't, the request fails with 400 (NO_PHONE_NUMBER_LINKED or NUMBER_NOT_WHATSAPP_ONBOARDED) and the flag is left unchanged. Disabling only clears the flag. There is no attach step here: WABA registration happens in the onboarding flow below.
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 | Retryable non-rate-limit Meta/network failure |
404 | Cross-tenant / unknown id (anti-enumeration) |
Cancel / reset#
DELETE /whatsapp/onboarding-sessions/{id} is the self-service recovery path:
pending/authorized/verifyingare 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_rejectedare already terminal: idempotent no-op, returns the current status (200).tier_pending/completed/liveare not cancellable: a verified/registered or live number returns409 WHATSAPP_ONBOARDING_NOT_CANCELLABLE.
Cancelling makes no Meta call (a re-onboard idempotently re-attaches the number) and fires no webhook, since 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.
Transfers do not work on WhatsApp calls
transferCall and dynamicTransfer tools are removed before a WhatsApp call starts. The
assistant is not told they are missing, so it simply has no way to hand the caller on. It
will keep trying to help rather than escalating.
This is easy to miss because it is source-dependent, not configuration-dependent: the same assistant transfers correctly on a phone call and silently cannot on WhatsApp. If an assistant handles both, give it a fallback the caller can use: a number to dial, or a capture form that takes a callback request.
Limits & FAQ#
- The signup link is single-use and valid for 15 minutes; if it expires or the owner abandons it, you simply start again. Nothing is left in a broken state.
- The number must already be answering ordinary voice calls: enablement is confirmed by an automated verification call to the line, so a number with no inbound voice routing can't complete onboarding.
- A brand-new WhatsApp Business Account can sit below Meta's calling tier; the number is parked as
tier_pendingand calling switches on once the account's limit rises. You're notified by webhook when it completes. - WhatsApp calls can't be transferred to your team. Keep lines whose flows depend on call transfers on the regular phone channel.
- WhatsApp calling and WhatsApp text are separate. Calling goes live through onboarding. Texting stays off until you set
whatsappEnabledon the assistant. Stickers and images are ignored; only customer-initiated text is answered.
Is it the same assistant as our phone line? Yes. WhatsApp calls to the number reach the assistant assigned to that number, and regular phone calls continue to work unchanged.
What does the caller see in WhatsApp? Your business display name. You request it during onboarding and Meta reviews it. Once approved, calling is live end-to-end under that name.
What does the business owner have to do? Only the Meta approval: they open the signup link, sign in, and consent. They never see a phone-number or verification-code screen. Note the Meta consent screen shows the platform provider's registered name. Meta does not white-label technology providers.
How do these calls show up in our reporting? Like phone calls: the start-of-call event carries call_source: "whatsapp" (see the assistant-request webhook), and each call delivers an end-of-call report and appears in your call history with its transcript and summary.
Do inbound WhatsApp texts get a reply? Yes, once the number is onboarded and whatsappEnabled is true on the assistant. Threads show up as conversations with channel: whatsapp. OnCore does not start the chat or send templates.
Next steps#
- WhatsApp events: signed webhooks for the onboarding lifecycle.
- Conversations: WhatsApp threads in your history.
- WhatsApp API: the full endpoint specification.