Connection & Setup
Connect a WhatsApp Business Account to a profile through Meta's Embedded Signup or a System User token, choose coexistence or Cloud API only, and connect a number you bought through Zernio.
When you finish this page a WhatsApp number is connected to a profile and you have its accountId. You need an API key, a profile id (Step 2 of the quickstart) and a Meta Business account at business.facebook.com; the WhatsApp Business Account (WABA) can be created during the flow. Every route ends on Meta's Cloud API through Embedded Signup or a System User token. There is no QR code pairing like WhatsApp Web.
Three rules apply whichever route you take:
- One WhatsApp number per profile. A profile holds exactly one WhatsApp number, so a second number goes on a second profile. A number can be live on one profile only.
- A Zernio-provisioned number is pinned to its profile. Connect it from the profile it was bought for; any other profile gets a
409. Move it first withPATCH /v1/whatsapp/phone-numbers/{id}/profileif it should live elsewhere. - Coexistence is the default. Unless you pass
onboarding=api, Embedded Signup offers to keep the number in the WhatsApp Business app, which limits the API (coexistence).
Step 1: Choose the number
Any number in a WABA can be connected. Where it comes from decides what else it can do:
| Number | How it connects | Calls and SMS |
|---|---|---|
| Bought or ported through Zernio | Embedded Signup; the number is pre-verified and appears as Verified in Meta's picker | Yes, on the same number |
| Your own number on your own carrier | Embedded Signup, or credentials | No; it has no Zernio line behind it |
Buying, porting, per-country pricing and KYC are on the Phone numbers pages; the WhatsApp-specific rules for a number are on WhatsApp phone numbers. A purchase starts this flow by itself, because connectWhatsapp defaults to true on POST /v1/phone-numbers/purchase.
Step 2: Start Embedded Signup
Call GET /v1/connect/whatsapp with profileId, redirect_url and onboarding. The response is a URL where the user runs Meta's Embedded Signup: log in to Meta, create or pick a WABA, pick a phone number. The same redirect flow connects every platform; no Facebook JavaScript SDK is needed and it works from any domain.
import Zernio from '@zernio/node';
const zernio = new Zernio();
const profileId = '66a1f0c2a4b9d3e8f1a2b3c4';
const { data: connect } = await zernio.connect.getConnectUrl({
path: { platform: 'whatsapp' },
query: { profileId, redirect_url: 'https://myapp.com/callback', onboarding: 'api' }
});
console.log(connect.authUrl);Response (200):
{
"authUrl": "https://www.facebook.com/v21.0/dialog/oauth?client_id=...",
"state": "..."
}onboarding picks the screen Meta shows:
onboarding | Meta shows | Result |
|---|---|---|
api | The WABA and number picker | Cloud API only. Use it for a Zernio-provisioned number, a number already on Cloud API elsewhere, or whenever you need groups or calling. |
business_app (default when omitted) | "Connect existing WhatsApp Business app" | Coexistence: the number stays in the app and works on the API with the limits below. |
Send the user's browser to authUrl. When they finish, they land on redirect_url with the connection appended:
https://myapp.com/callback?connected=whatsapp&profileId=66a1f0c2a4b9d3e8f1a2b3c4&accountId=66b2e19d8c3f5a7e9d0b1c2d&username=%2B13105551234accountId is the id every WhatsApp call takes from here on. If Embedded Signup fails, the browser lands on the same redirect_url with error and platform appended; the WhatsApp values are whatsapp_error, one_whatsapp_per_profile, whatsapp_number_already_connected, whatsapp_number_pinned_to_profile and connection_cancelled.
Headless mode: pick the number yourself
WhatsApp grants access per WABA. When the WABA the user picked holds 2 or more numbers, a headless flow (headless=true on the call above) sends the user to your redirect_url with step=select_phone_number, profileId and tempToken; a single-number WABA connects in the callback and never reaches this step. List the numbers with GET /v1/connect/whatsapp/select-phone-number, show your own picker, then bind one:
const params = new URL(requestUrl).searchParams; // requestUrl: the URL your handler received
const tempToken = params.get('tempToken')!;
const { data: numbers } = await zernio.connect.whatsapp.listWhatsAppPhoneNumbers({
query: { profileId, tempToken }
});
const chosen = numbers.phoneNumbers[0];
const { data: selected } = await zernio.connect.whatsapp.completeWhatsAppPhoneSelection({
body: { profileId, phoneNumberId: chosen.id, wabaId: chosen.wabaId, tempToken }
});
console.log(selected.account.accountId);The list returns the numbers across the user's WABAs.
Response (200):
{
"phoneNumbers": [
{
"id": "1875844705851813",
"display_phone_number": "+1 310-555-1234",
"verified_name": "Acme Corp",
"quality_rating": "GREEN",
"name_status": "APPROVED",
"messaging_limit_tier": "TIER_1K",
"wabaId": "317766992490131",
"wabaName": "Acme WABA"
}
]
}The selection binds the number, exchanges the short-lived token for a long-lived one and subscribes the WABA to webhooks.
Response (200):
{
"message": "WhatsApp phone number connected successfully",
"account": {
"accountId": "66b2e19d8c3f5a7e9d0b1c2d",
"platform": "whatsapp",
"username": "+1 310-555-1234",
"displayName": "Acme Corp",
"isActive": true,
"selectedPhoneNumber": "+1 310-555-1234"
}
}If you render Meta's Embedded Signup popup yourself with the Facebook JavaScript SDK, the authorization code never passes through a redirect_url. Post it to POST /v1/connect/whatsapp/embedded-signup with code and profileId (plus wabaId, phoneNumberId, isCoexistence and expectedPhoneNumber when the SDK reported them) to finish the connection (Connect from Embedded Signup).
WhatsApp Business app coexistence
A business that already uses the WhatsApp Business app can keep the number there and connect it to the Cloud API at the same time. Embedded Signup offers this when onboarding is business_app or omitted. The number then works in both places: messages are mirrored between the app and the API (up to 6 months of history is synced), contacts from the app are imported, and the business can keep sending individual messages from the app.
Coexistence changes what the API can do on that number:
| Feature | On a coexistence number |
|---|---|
| Throughput | Fixed at 20 messages per second |
| Groups created in the app | Not synced; not visible through the API |
| Groups API | Not supported. Needs a Cloud API-only number |
| Voice and video calls | Not supported through the API |
| Disappearing messages | Turned off for all 1:1 chats |
| View once messages | Disabled for all 1:1 chats |
| Broadcast lists | Disabled in the WhatsApp Business app |
| Profile photo | Locked; manage it in the app |
Everything else uses the same endpoints as a Cloud API-only number. message.sent carries source: "whatsapp_business_app" for sends made from the phone, so you can tell them from API sends.
Meta has no API to take a number out of coexistence. The business disconnects on the phone (WhatsApp Business app > Settings > Account > Business Platform > Disconnect). Meta notifies Zernio, the account is deactivated and an account.disconnected event fires within seconds with Meta's own reason (how detection works). Meta's notification is best-effort, so never read the absence of that event as proof the channel is alive; confirm with the liveness check. Then reconnect the number with onboarding=api, or with credentials, and the Groups API is available.
Connect with credentials (headless)
If you hold Meta credentials already, connect without a browser: server-to-server integrations, CLI tools and automated provisioning. Create a System User in Meta Business Suite, generate a permanent token with whatsapp_business_management and whatsapp_business_messaging (add whatsapp_business_manage_events for Click-to-WhatsApp conversions), and copy the WABA id and phone number id from WhatsApp Manager > Account Tools > Phone Numbers.
Call POST /v1/connect/whatsapp/credentials with profileId, accessToken, wabaId and phoneNumberId. Add pin when the number has two-step verification on; without it Meta rejects the registration.
const { data: credentials } = await zernio.connect.whatsapp.connectWhatsAppCredentials({
body: {
profileId,
accessToken: 'EAABsbCS...your-system-user-token',
wabaId: '317766992490131',
phoneNumberId: '1875844705851813',
pin: '481902'
}
});
console.log(credentials.account.accountId);Response (200):
{
"message": "WhatsApp connected successfully",
"account": {
"accountId": "66b2e19d8c3f5a7e9d0b1c2d",
"platform": "whatsapp",
"username": "+1 310-555-1234",
"displayName": "Acme Corp",
"isActive": true,
"phoneNumber": "+1 310-555-1234",
"verifiedName": "Acme Corp",
"qualityRating": "GREEN"
}
}Zernio validates the credentials against Meta, creates the account, subscribes the WABA to webhooks and registers the number on the Cloud API. When phoneNumberId is not in the WABA, the 400 lists availablePhoneNumbers so you can correct it.
Connecting subscribes your Meta app to this WABA with a callback override that routes its webhook delivery to Zernio. Any callback URL you had configured on the WABA stops receiving events at once, with no overlap window. Do not unsubscribe your app from the WABA afterwards: that also cuts off Zernio's delivery, and recovery means calling this endpoint again.
Scopes
Embedded Signup requests these scopes; the scopes section of the connecting guide explains how they are granted and checked.
| Scope | What it enables |
|---|---|
whatsapp_business_messaging | Send and receive messages |
whatsapp_business_management | Manage WABA assets: templates, phone numbers, settings |
whatsapp_business_manage_events | Conversions API events and CTWA dataset provisioning |
business_management | Discover WABAs owned by your Meta Business during connection |
A System User token you mint yourself carries only the permissions you assigned; Zernio cannot extend it.
From the dashboard
The same flow runs from Connections when you connect by hand.
-
Open Connections, find the WhatsApp card and click + Connect.

-
Choose Get a new number (Zernio provisions and verifies it; the price per country shows before you confirm) or Use my own number.

-
Pick the country and click Confirm. US and other instant countries verify in about 30 seconds; regulated countries need a one-time KYC form first, and the number activates within 1 to 3 business days, with an email and the
whatsapp.number.activatedevent when it is ready.
-
Wait while Zernio verifies the number with Meta, about 30 seconds.

-
Click Continue to WhatsApp setup.

-
In Meta's Embedded Signup, create a WABA or pick an existing one.

-
Select the number, already marked Verified, and continue.

-
The account appears in Connections with a connected status.

-
Settings on the connection opens the templates list.

-
The Business Profile tab sets the picture, display name, about text, description and contact details.

In step 6, do not choose Connect existing WhatsApp Business app account if you plan to use the Groups API or calling. That option activates coexistence, which disables both. Create a new WABA or pick a number that is not in the WhatsApp Business app.
If it fails
A 409 from the credentials or selection call means the number is already spoken for. code says which rule it hit:
code | Meaning | Fix |
|---|---|---|
ONE_WHATSAPP_PER_PROFILE | The profile already holds a WhatsApp number | Connect this number to a different or new profile |
WHATSAPP_NUMBER_PINNED_TO_PROFILE | A Zernio-provisioned number pinned to another profile | Connect from that profile, or move it first with PATCH /v1/whatsapp/phone-numbers/{id}/profile |
WHATSAPP_NUMBER_ALREADY_CONNECTED | The number is live on another profile or team | Disconnect it there first |
The redirect flow reports the same three as error=one_whatsapp_per_profile, whatsapp_number_pinned_to_profile and whatsapp_number_already_connected on your redirect_url. A 402 is a billing gate, described in connecting accounts. Once connected, a number that sends (#200) You do not have the necessary permission on every message has a two-step PIN Meta rejected; register it again with the PIN on WhatsApp phone numbers.
Related
- WhatsApp phone numbers: number status, the liveness check, registering with a PIN.
- Phone numbers: buy, port, KYC, per-country pricing.
- Connecting accounts: the redirect flow and headless mode shared by every platform.
- Templates: the first thing to create after connecting.
- Group chats: why a Cloud API-only number matters.
Send template messages, broadcasts, flows and replies from a WhatsApp Business Account with the Zernio API, plus groups, calling and Click-to-WhatsApp attribution.
Broadcasts
Send an approved WhatsApp template to many recipients with per-recipient variables, now or at a scheduled time, with delivery tracking per recipient.