WhaBuzzdocs
Guides

Onboard your customer's number

Send your customer to a hosted WhaBuzz page that connects their WhatsApp number, then verify the result on your server. Redirect or embedded modal, with webhooks and the states a session moves through.

A connect session is a one-time link to a WhaBuzz-hosted page where your customer connects their own WhatsApp number to your account. You create the session, send the customer to the link, and learn the outcome from the return redirect and a webhook. The customer never needs a WhaBuzz login, and Meta's access token never reaches your server.

Use it when you run a product for other businesses and each of them brings a number. If you connect your own number, use the console. If an app of yours needs access to a number a WhaBuzz customer already owns, use Connect your app.

#Who does what

You can also make a link from the console: on the Tenants page, Connect link creates a session for an active tenant, copies the link, and you send it to the business.

#Quickstart

#1. Create a session

With a tenant API key (or an app key plus X-WhaBuzz-Tenant). A key bound to one number is refused with 403.

bash
curl -X POST https://api.whabuzz.com/v1/connect-sessions \
  -H "Authorization: Bearer $WHABUZZ_KEY" -H "Content-Type: application/json" \
  -d '{
    "return_url": "https://app.example.com/whatsapp/done",
    "cancel_url": "https://app.example.com/whatsapp",
    "branding": { "product_name": "Acme CRM", "accent": "#1F6FEB" }
  }'
json
{
  "id": "cs_01JAB3N2R5T8W1Y4A7C0E3G6J9",
  "url": "https://app.whabuzz.com/connect/s/9fK2mQ7xL4pV1sN8cR3t…",
  "expires_at": "2026-10-22T10:15:00.000Z",
  "state": "created"
}

url is the only copy of the link. Store the id; send the customer to url.

FieldMeaning
return_urlRequired. https, at most 2048 characters, no credentials, no #fragment.
cancel_urlOptional. Where a customer who backs out goes. Defaults to return_url.
branding.product_name1 to 60 characters. Shown on the page and in "Return to …".
branding.logo_urlhttps URL of your logo.
branding.accentHex colour such as #1F6FEB.
options.coexistencetrue to connect a number that also keeps running on the WhatsApp Business app. See Coexistence.
options.allow_template_importDefault true. Offer to copy approved templates from the business's other WhatsApp accounts.
options.existing_number_e164The number you expect them to connect, such as +919876543210.
allowed_originsUp to 5 bare https origins that may embed the page (embedded mode). Defaults to the origin of return_url. whabuzz.com is never allowed.

#2. Send the customer to the link

Redirect the browser to url. The link is one-time and must be opened within 15 minutes. Opening it binds that browser to the session for 24 hours. A second open answers 410, so do not let link scanners or previews fetch it.

The customer then sees:

  1. Meta's Embedded Signup popup.
  2. A phone re-verification step, only if Meta asks for one.
  3. An offer to import templates from the business's other WhatsApp accounts, if you allowed it.
  4. A payment-method step (the WhatsApp Manager billing page). See billing_state.
  5. Return to your product.

#3. Handle the return and verify it

The customer arrives at return_url (or cancel_url after a cancel) with three query parameters:

https://app.example.com/whatsapp/done?session_id=cs_01JAB…&state=completed&result_token=1761128400.Xq…

Do not trust the query string. Anyone can type it. Confirm it from your server:

bash
curl "https://api.whabuzz.com/v1/connect-sessions/cs_01JAB3N2R5T8W1Y4A7C0E3G6J9?result_token=1761128400.Xq…" \
  -H "Authorization: Bearer $WHABUZZ_KEY"
json
{
  "id": "cs_01JAB3N2R5T8W1Y4A7C0E3G6J9",
  "state": "completed",
  "result": {
    "number_id": "6f1c2d9e-3b4a-4c5d-8e7f-0a1b2c3d4e5f",
    "phone_e164": "+919876543210",
    "phone_number_id": "1013622581841046",
    "waba_id": "26059971920328384",
    "sibling_accounts": null,
    "templates_imported": 4,
    "billing_state": null
  },
  "error": null,
  "attempts": 1,
  "result_token": { "valid": true, "state": "completed" }
}

Trust state and result from this response only when result_token.valid is true. A forged, malformed or expired token (they last 10 minutes) answers 400 invalid_result_token; read the session without the token instead. Then save result.phone_number_id (and number_id) against your customer.

In Node:

js
const s = await wb.connectSessions.get(sessionId, { resultToken });
if (s.result_token?.valid && s.state === 'completed') await saveNumber(customerId, s.result.phone_number_id);

#4. Listen for the webhook

The return redirect only happens if the customer's browser makes it back. Subscribe an endpoint to connect.completed and connect.failed (events) so you hear about every session that ended, with or without the redirect. Each session sends at most one event. Never-opened sessions that expire, and cancelled sessions, send nothing.

#Embedded mode

To keep the customer on your page, open the link in a modal instead of redirecting:

html
<script src="https://app.whabuzz.com/v1/connect.js"></script>
<script>
  // url: the `url` your server got from POST /v1/connect-sessions
  WhaBuzz.connect({
    url,
    onComplete({ session_id, state, result_token }) {
      // Send all three to your server and verify them as in step 3.
      fetch('/whatsapp/verify', { method: 'POST', body: JSON.stringify({ session_id, state, result_token }) });
    },
    onCancel({ session_id, state, result_token }) {
      // The customer closed the modal without connecting.
    },
  });
</script>

If your site sets a Content-Security-Policy, allow WhaBuzz in these directives:

script-src https://app.whabuzz.com;
frame-src  https://app.whabuzz.com;

#Direct mode: Meta's popup on your page

To skip the WhaBuzz page entirely, run Meta's Embedded Signup popup on your own page and hand the result to WhaBuzz from your server. This needs your domain on the WhaBuzz Meta app's allowed domains first: ask WhaBuzz support. Until then, Meta refuses the popup on your page, so keep the hosted url as the fallback.

  1. Create the session with "mode": "direct". The answer carries the hosted url (keep it for the fallback) and an embedded_signup block:
json
{
  "id": "cs_01JAB3N2R5T8W1Y4A7C0E3G6J9",
  "url": "https://app.whabuzz.com/connect/s/9fK2mQ7xL4pV1sN8cR3t…",
  "state": "created",
  "embedded_signup": {
    "es_enabled": true,
    "meta_app_id": "28719689174328309",
    "config_id": "1021969937569247",
    "graph_version": "v26.0",
    "es_version": "v4",
    "feature_type": null
  }
}
  1. Load Meta's JavaScript SDK before the customer clicks, then call FB.login inside the click (browsers block popups opened later):
js
FB.init({ appId: es.meta_app_id, version: es.graph_version, xfbml: false });
FB.login(onLogin, {
  config_id: es.config_id, response_type: 'code', override_default_response_type: true,
  extras: es.es_version === 'v2'
    ? { setup: {}, sessionInfoVersion: '3' }
    : es.feature_type ? { featureType: es.feature_type } : {},
});

Listen for message events from an origin ending in facebook.com whose type is WA_EMBEDDED_SIGNUP. FINISH (or FINISH_WHATSAPP_BUSINESS_APP_ONBOARDING for coexistence) carries waba_id and phone_number_id; CANCEL carries the step the customer left at.

  1. Send the code and the two ids to your server, and from there:
bash
curl -X POST https://api.whabuzz.com/v1/connect-sessions/cs_01JAB3N2R5T8W1Y4A7C0E3G6J9/complete \
  -H "Authorization: Bearer $WHABUZZ_KEY" -H "Content-Type: application/json" \
  -d '{ "code": "AQBx…", "waba_id": "26059971920328384", "phone_number_id": "1013622581841046" }'

The answer is the session:

stateDo this
completedSave result.phone_number_id. connect.completed is sent. Template import and the payment-method step are yours to offer.
needs_verificationSend the customer to the hosted url to verify the phone. The link still opens once, within 15 minutes of creating the session.
opened with errorThe attempt failed; error.message has Meta's words. Let them try again (up to 5 attempts), or offer the hosted url.
failedFive attempts failed. connect.failed is sent. Create a new session.

#States

StateMeaningFinal
createdThe link exists and has not been opened.No
openedThe customer opened it. The browser is bound for 24 hours.No
connectingMeta's signup finished; WhaBuzz is connecting the number.No
needs_verificationMeta wants the number verified by SMS or voice call.No
connectedThe number is connected. The customer may still import templates or set up billing.No
importingTemplates are being copied from another WhatsApp account.No
completedThe customer finished, or left a connected session idle for 30 minutes.Yes
failedFive connect attempts failed. error says why.Yes
expiredThe link was not opened in 15 minutes, or the bound browser did not finish in 24 hours.Yes
cancelledThe customer backed out before connecting.Yes

A failed attempt returns the session to opened so the customer can try again, up to 5 attempts (attempts). A customer who leaves after the number connected is completed, not cancelled: the number is yours. A 10-minute sweep completes idle connected sessions and expires abandoned ones.

#Result

result is null until a number is connected.

FieldMeaning
number_idWhaBuzz's internal id for the number (a UUID).
phone_e164The number, such as +919876543210.
phone_number_idMeta's id for the number. Use it with /v1/numbers and as from when sending.
waba_idThe WhatsApp Business Account the number belongs to.
sibling_accountsThe business's other WhatsApp accounts and their approved templates, or null when not checked. See Offer to import templates.
templates_importedTemplates copied during this session.
business_verification_statusMeta's verification state of the business portfolio, or null when not read yet. verified when done. Anything else: the business verifies itself in Meta Business Manager, and its messaging limits stay lower until it does. The hosted page tells the business this on its last step.
billing_stateMay be null. Meta delivers business-initiated (template) messages only after the WhatsApp account has a payment method; until then Meta answers error 131042. Check this field before your first template send.

#Webhooks

EventSent when
connect.completedThe session reached completed.
connect.failedThe session reached failed, or expired after the link was opened.

data carries session_id, state, result, error and product_name. Number fields in the envelope (phone_number_id, display_phone_number, waba_id) are null when no number connected. Full payloads: WhaBuzz events.

#Security

#Errors

StatusCodeCause
400invalid_requestA field failed validation (non-https URL, bad accent, more than 5 origins, whabuzz.com in allowed_origins). message names it.
400invalid_result_tokenThe result_token is forged, malformed or older than 10 minutes.
401unauthorizedMissing or invalid key.
403forbiddenThe key is bound to one number, or lacks permission to manage.
403es_disabledConnecting numbers is switched off right now (complete).
403owner_suspendedThe owner account is suspended. Contact WhaBuzz support.
404not_foundNo such session on your account.
409not_direct, invalid_statecomplete on a hosted session, or a session that is connecting, waiting for verification, or over.

The customer sees 404 link_invalid, 410 link_expired (not opened within 15 minutes) or 410 link_used (already opened) on the hosted page when the link is bad. Create a new session.

#Limits

LimitValue
Open the link within15 minutes
Browser binding24 hours
Connect attempts5
result_token lifetime10 minutes
Idle connected session completes after30 minutes
allowed_origins5
return_url, cancel_url2048 characters

List sessions with GET /v1/connect-sessions (newest first, limit and before). The same calls are in the Node SDK.

This page as Markdown: /onboard-a-number.md. All docs in one file: /llms-full.txt. Status.