Skip to main content

bZapper Connect

bZapper Connect is for partner software: CRMs, ERPs, customer-service platforms and vertical systems that want to offer WhatsApp to their own customers.

Your customer clicks "Turn on WhatsApp" inside your product. The bZapper component opens, already filled with their details. Right there they:

  1. create the account on bZapper (no password, no captcha, no email confirmation — they are already signed in to your product);
  2. subscribe to bZapper Pro (card or Pix);
  3. connect the number (QR code or pairing code).

At the end, your backend receives an API key for the customer's account and starts sending and receiving messages with it. The customer never leaves your screen.

Who is whose customer

Your customer becomes a direct bZapper customer: the subscription, invoices and data are theirs. You get a credential they authorized, which they can disconnect at any time under Connected apps. You never see a card or a password.

Flow overview​

 Your backend               Your front end (browser)          bZapper
│ │ │
│ 1. POST /partner/connect-sessions (bz_partner_…) ─────────▶│
│◀──────────────── session_token (cs_…, 30 min) ─────────────│
│── session_token ───────────▶│ │
│ │ 2. BzapperConnect.open(...) ─▶│ account → Pro → number
│ │◀── onComplete({ code }) ──────│
│◀────────── code ────────────│ │
│ 3. POST /partner/connect/exchange { code } ───────────────▶│
│◀──────────────── api_key (bz_live_…) + connection ─────────│
│ 4. use the api_key as usual (send, receive, webhooks) │
│◀═══════════ connect.* + message.* webhooks ════════════════│
CredentialLives inUsed to
bz_partner_… (partner secret)your backend onlyopen sessions, exchange the code, manage connections
cs_… (session token)browser, 30 minopen the component
cc_… (completion code)browser → your backend, 10 min, single useexchange for the API key
bz_live_… (customer API key)your backend onlyoperate the customer's WhatsApp

0. Partner registration​

Partners are registered by the bZapper team. You provide:

  • name and logo (shown to the customer on the authorization screen). The logo is uploaded as a file by the bZapper team — PNG, JPEG, WebP or SVG, up to 2 MB — and is then served from bZapper's CDN, so it never breaks when you change your own site;
  • allowed origins: the domains where the component runs (e.g. https://app.yourproduct.com). Anything else gets 403 origin_not_allowed;
  • webhook URL that receives the events of all your connections.

You receive the partner secret (bz_partner_…) and the webhook secret. Both are shown only once.

1. Open the session (your backend)​

When the customer clicks "Turn on WhatsApp", your backend calls:

curl -X POST https://api.bzapper.com.br/partner/connect-sessions \
-H "Authorization: Bearer $BZAPPER_PARTNER_SECRET" \
-H "Content-Type: application/json" \
-d '{
"external_id": "customer-4821",
"customer": {
"name": "Ana Souza",
"email": "[email protected]",
"company": "Boxy Pharma",
"phone": "+5511988887777",
"country": "BR",
"locale": "pt-BR"
}
}'
{
"session_token": "cs_8a82868d9a91…",
"expires_at": "2026-09-17T17:20:00Z",
"connection": { "id": "7ece7f98-…", "external_id": "customer-4821", "status": "pending_account" }
}
  • external_id is the customer id in your system. The same external_id always reuses the same connection. Calling it again after completion opens the component in manage mode.
  • customer.company becomes the account and project name on bZapper ("Boxy Pharma"). Without a company, we use the person's name.
  • customer.phone is pre-filled on the WhatsApp step.
  • customer.country sets the currency (BR → BRL; Americas → USD; others → EUR). Pix only shows up in BRL.
The secret never goes to the browser

Only the session_token goes to the front end. It lasts 30 minutes and only opens the component from your registered origins.

What if the customer already has a bZapper account?​

The component always asks "Do you already have a bZapper account?" — Create new account or I have an account. If the email you sent already has an account, the screen opens straight on I have an account, with "We found your account". The customer can also link using another email (their bZapper account's email, if it differs from the one in your system).

To link, the person goes through one extra step: a 6-digit code we send to the account's email, and that email must be an admin of the account. Without it, any system that knew someone's email could get access to that person's account. After the code, the flow is the same (if the account is already Pro, payment is skipped).

2. Open the component (your front end)​

Load the script once:

<script src="https://widget.bzapper.com.br/v1/connect.js"></script>
async function turnOnWhatsApp() {
const { session_token } = await fetch('/api/bzapper/session', { method: 'POST' }).then((r) => r.json());

BzapperConnect.open({
session: session_token,
onComplete: async ({ code }) => {
await fetch('/api/bzapper/complete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code }),
});
},
onClose: () => console.log('closed'),
onError: ({ code }) => console.warn('bZapper Connect:', code),
});
}

BzapperConnect.open() returns { close() } if you need to close it from code.

Inline​

<bzapper-connect data-session="cs_8a82868d9a91…"></bzapper-connect>

<script>
document.querySelector('bzapper-connect')
.addEventListener('bzapper:complete', (e) => sendToBackend(e.detail.code));
</script>

Options and events​

Option (open)Attribute (inline)Description
sessiondata-sessionRequired. The session_token from step 1.
localedata-localeLanguage (pt, en, es, it, de, fr). Default: <html lang> or the browser's.
apiBasedata-apiAPI base. Default: https://api.bzapper.com.br.
CallbackDOM eventdetail
onReadybzapper:ready{ step, connectionId, status }
onStepbzapper:step{ step }: account, verify_email, payment, number, manage, revoked
onCompletebzapper:complete{ code, connectionId, externalId }
onClosebzapper:close— (modal only)
onErrorbzapper:error{ code, message } (e.g. connect_session_expired)

The component uses Shadow DOM, so your site's CSS does not affect it (and its CSS does not affect your site). The card form is Stripe's own; card numbers never go through your code or ours.

CSP

If your site uses a Content-Security-Policy, allow: script-src https://widget.bzapper.com.br https://js.stripe.com; connect-src https://api.bzapper.com.br; frame-src https://js.stripe.com https://hooks.stripe.com;

3. Exchange the code for the API key (your backend)​

curl -X POST https://api.bzapper.com.br/partner/connect/exchange \
-H "Authorization: Bearer $BZAPPER_PARTNER_SECRET" \
-H "Content-Type: application/json" \
-d '{ "code": "cc_4291a95ab388…" }'
{
"id": "7ece7f98-…",
"external_id": "customer-4821",
"status": "active",
"account_id": "08eaa5be-…",
"project_id": "88a27b8c-…",
"api_key": "bz_live_45cd5a…",
"numbers": [{ "id": "209bd3cd-…", "phone": "+5511988887777", "status": "connected" }]
}

Store the api_key with your customer (external_id). It is not shown again. The code works once and for 10 minutes.

Lost the key, or the customer closed the tab before the exchange?

You get the connect.completed webhook anyway. Call POST /partner/connections/{id}/rotate-key to issue a new one (the previous key stops working immediately).

4. Use the API key​

It is a regular bZapper API key, bound to the connection's project. It covers everything that operates their WhatsApp: messages, numbers, groups, conversations, presence, labels, calls, pools and campaigns — plus POST /contacts/check (does this number have WhatsApp).

Three limits are deliberate:

  • Project, not account. Numbers in the customer's other projects do not exist for that key (404), even though the owner is the same.
  • Nothing about the account or money. Plan and billing, users, other API keys, account webhooks, projects and account deletion answer 403 forbidden.
  • No GET /stream, no account address book. The SSE feed is filtered per account and carries the QR and pairing codes; the contact base (/contacts, /tags, /contact-groups, /suppressions, /blocklist) is account-wide too. You get events through your webhook, already filtered per connection.

Connection lifecycle​

statusMeaningThe key
pending_accountSession open, account not created yet—
pending_paymentAccount created, Pro not paid—
pending_numberPro paid, WhatsApp not connected—
activeCompletedworks
suspendedThe customer's Pro is unpaidanswers 402 connect_suspended
revokedEnded (by the customer, by you, or account deletion)answers 401 connect_revoked

A connection only exists while paid. The partner channel has no Free plan. If the Pro renewal fails, the connection becomes suspended and goes back to active by itself as soon as the customer pays. To let them pay, just open the component again (same external_id): it goes straight to the payment screen, with a notice.

Revocation is final

When the customer disconnects you under Connected apps, the connection becomes revoked and releases the account: the key dies and any call on an open session answers 409 connection_revoked. Opening the component again with the same external_id restarts at the account step — with the code sent to the customer's email. You cannot reconnect on your own; whoever disconnected has to authorize again.

Handling suspension in your code​

const res = await fetch('https://api.bzapper.com.br/messages/text', {
method: 'POST',
headers: { Authorization: `Bearer ${customer.bzapperKey}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ to: '+5511999990000', body: 'Your order is on its way' }),
});
if (res.status === 402) {
const { code } = await res.json();
if (code === 'connect_suspended') showFixWhatsAppBanner(); // reopens Connect
}

Partner webhooks​

All events of your connections arrive at the partner webhook URL, with the same signature as bZapper webhooks (X-Bzapper-Signature: sha256=<hex>, HMAC-SHA256 of the raw body with the partner webhook secret). It is the usual envelope plus a connection block so you know which customer it belongs to:

{
"event_id": "evt_3f…",
"event_type": "message.received",
"timestamp": "2026-09-17T16:55:02Z",
"instance_id": "209bd3cd-…",
"payload": { "type": "text", "from": "+5511999990000", "body": "Hi!" },
"connection": {
"id": "7ece7f98-…",
"external_id": "customer-4821",
"account_id": "08eaa5be-…",
"project_id": "88a27b8c-…",
"status": "active"
}
}

Lifecycle:

EventWhen
connect.completedThe customer finished (Pro paid + WhatsApp connected)
connect.suspendedThe customer's Pro is no longer paid
connect.resumedThe customer paid and the connection is back
connect.revokedThe connection ended. payload.revoked_by: customer, partner or account_deleted

Operations (only for active connections): the same bZapper project events, such as message.*, instance.* and contact.opted_out. QR codes and pairing codes are not forwarded: they are the secret of the customer's device.

No webhook needs to be registered on the customer's account. Retries: up to 5, with exponential backoff. To reconcile, use GET /partner/connections.

Managing connections (backend)​

MethodRoutePurpose
GET/partner/meWho you are (checks the secret)
GET/partner/connections?external_id=&status=List connections
GET/partner/connections/{id}Status, account, project and numbers
POST/partner/connections/{id}/rotate-keyNew API key (the previous one dies)
DELETE/partner/connections/{id}End the connection (does not cancel the customer's plan)

On the customer side: Connected apps​

In the bZapper dashboard, under Connected apps, the customer sees the software linked to their account and can disconnect any of them. The partner key stops right away and you receive connect.revoked. The plan and numbers stay with the customer.

An account created through Connect has no password. If the customer wants to open the dashboard directly (invoices, cards), they use the welcome link sent by email or "Forgot password" on the login screen.

Full reference

Every endpoint, field, state and error code is in Reference — bZapper Connect.

SDKs​

All 5 official SDKs include the partner client. See SDKs.

SDKPartner client
Nodenew BzapperPartner({ partnerSecret })
PythonPartnerClient(partner_secret)
Gobzapper.NewPartnerClient(secret)
PHPnew Bzapper\PartnerClient($secret)
JavaBzapperPartner

Errors​

CodeHTTPWhereWhat to do
partner_unauthorized401/partner/*Missing, wrong or rotated secret
partner_inactive403allIntegration disabled by bZapper
external_id_required / customer_email_required / customer_name_required400create sessionFill in the data
origin_not_allowed403componentThe page's domain is not a registered origin
connect_session_expired401componentOpen a new session
invalid_code400exchangeWrong, expired (10 min) or used code → use rotate-key
connection_revoked409componentThe customer disconnected you: open a new session (it restarts at the account step)
payment_pending409paymentThe previous attempt is being confirmed (Pix/3DS). Wait a few seconds
account_admin_required403existing accountThe email has a bZapper account but is not an admin of it
code_attempts_exceeded429existing accountToo many wrong attempts for that email in the last hour
code_sends_exceeded429existing accountToo many codes sent to that email in the last hour
account_not_found404existing account"I have an account" with an email that has no bZapper account
connection_not_active409rotate-keyConnection not completed or ended
connect_suspended402API keyCustomer's Pro unpaid → reopen Connect
connect_revoked401API keyConnection ended → reopen Connect to reconnect