Pular para o conteúdo principal

Referência — bZapper Connect

Esta página é a referência técnica completa do Connect. Para o passo a passo de implantação, comece pelo guia do bZapper Connect.

Base da API: https://api.bzapper.com.br · Componente: https://widget.bzapper.com.br/v1/connect.js

1. Modelo mental​

O Connect liga três partes, cada uma com a sua credencial:

ParteCredencialOnde viveAlcance
Seu backendbz_partner_… (secret do parceiro)só no seu servidorabrir sessões, trocar o code, gerir suas conexões
O componente no navegador do clientecs_… (ingresso da sessão, 30 min)navegador, na sua páginaoperar uma conexão: conta, pagamento, número
Seu backend, operando o WhatsAppbz_live_… (API key do cliente)só no seu servidoro WhatsApp do projeto da conexão

A entidade durável é a conexão: um cliente seu (external_id) ligado a uma conta bZapper. Ela é única por (parceiro, external_id) — o mesmo external_id sempre cai na mesma conexão, o que torna POST /partner/connect-sessions idempotente.

O code (cc_…) existe só para atravessar o navegador sem carregar a API key: ele é de uso único, vale 10 minutos e só é trocável pelo parceiro dono da conexão.

2. Autenticação​

SuperfícieComo autenticaObservações
/partner/*Authorization: Bearer bz_partner_…Só header. O secret não é aceito em query string.
/connect/*X-Connect-Session: cs_…Em GET /connect/stream (SSE) também vale ?session=cs_…, porque o EventSource não manda header. Nas demais rotas a query é recusada.
API do cliente (/messages, /instances…)Authorization: Bearer bz_live_…A key entregue pela troca do code.

Além da credencial, /connect/* confere o header Origin contra as origens cadastradas do parceiro (comparação exata, sem curinga; maiúsculas e barra final são normalizadas). Origem fora da lista: 403 origin_not_allowed.

O que a origem protege — e o que não

A allowlist de origens protege o cliente contra outra página tentar abrir o componente. Ela não é um limite contra você, parceiro: Origin é um header comum, que qualquer chamada de servidor pode escrever. O que autentica /connect/* é o ingresso cs_…; trate-o como segredo e não o registre em log.

3. Endpoints do parceiro (/partner/*)​

GET /partner/me​

Confere o secret e devolve o seu cadastro.

{ "id": "…", "slug": "bfocus", "name": "bFocus", "logo_url": "https://…",
"allowed_origins": ["https://app.bfocus.com.br"],
"webhook_url": "https://api.bfocus.com.br/webhooks/bzapper",
"key_scopes": ["instances:read","instances:write","messages:send","contacts:check","presence:write","media:read"] }

POST /partner/connect-sessions​

Abre (ou reaproveita) a conexão do seu cliente e emite o ingresso do componente.

CampoTipoObrigatórioDescrição
external_idstring (≤200)simO id do cliente no seu sistema. Use um id estável, nunca rotacionado.
customer.namestringname ou companyNome da pessoa.
customer.emailstringsimDefine o caminho do fluxo (conta nova × conta existente).
customer.companystring—Vira o nome da conta e do projeto no bZapper.
customer.phonestring (E.164)—Preenche o passo do WhatsApp.
customer.countrystring (ISO-3166 alpha-2)—Define a moeda: BR→BRL, Américas→USD, demais→EUR. Pix só em BRL.
customer.localestring (BCP-47)—Idioma do componente e dos e-mails.
{ "session_token": "cs_8a82…", "expires_at": "2026-09-17T17:20:00Z",
"connection": { "id": "7ece…", "external_id": "cliente-4821", "status": "pending_account" } }

Erros: 400 external_id_required, 400 customer_email_required, 400 customer_name_required, 401 partner_unauthorized, 403 partner_inactive.

Dados do cliente são sugestão, não identidade

Enquanto a conta não está vinculada, cada nova sessão atualiza o prefill. Depois de vinculada, os dados são do cliente e você não os reescreve. Quem prova o e-mail é o próprio bZapper (conta nova) ou o código enviado a ele (conta existente).

POST /partner/connect/exchange​

Troca o code pela API key do cliente. Uso único.

{ "code": "cc_4291…" }
{ "id": "7ece…", "external_id": "cliente-4821", "status": "active",
"account_id": "08ea…", "project_id": "88a2…",
"api_key": "bz_live_45cd…",
"numbers": [{ "id": "209b…", "phone": "+5511988887777", "status": "connected" }] }

A api_key não é mostrada de novo. Erros: 400 code_required, 400 invalid_code (errado, vencido, já usado, ou de outro parceiro), 409 connection_revoked.

GET /partner/connections​

Lista as suas conexões. Filtros: external_id, status. Devolve { "data": [...] } com o objeto de conexão.

GET /partner/connections/{id}​

Uma conexão, com numbers[] atualizado. 404 connection_not_found se não for sua.

POST /partner/connections/{id}/rotate-key​

Cunha uma nova API key e revoga a anterior na hora. Use quando perdeu a key ou quando soube da conclusão pelo webhook sem ter trocado o code.

Erros: 404 connection_not_found, 409 connection_not_active (ainda não concluída ou já encerrada).

DELETE /partner/connections/{id}​

Encerra a conexão do seu lado: revoga a key e dispara connect.revoked com revoked_by: "partner". Não cancela o plano do cliente — a assinatura é dele com o bZapper. Responde 204.

4. Objeto de conexão​

{
"id": "7ece7f98-…",
"external_id": "cliente-4821",
"status": "active",
"account_id": "08eaa5be-…",
"project_id": "88a27b8c-…",
"customer": { "name": "Ana Souza", "email": "[email protected]",
"company": "Boxy Pharma", "phone": "+5511988887777", "country": "BR" },
"numbers": [{ "id": "209bd3cd-…", "phone": "+5511988887777", "status": "connected" }],
"activated_at": "2026-09-17T16:55:02Z",
"suspended_at": null,
"revoked_at": null,
"created_at": "2026-09-17T16:40:00Z"
}

Em GET /me/connections (o cliente, no painel dele) o mesmo objeto vem com partner_name e partner_logo_url, e sem api_key.

5. Máquina de estados​

pending_account ──conta vinculada──▶ pending_payment ──Pro pago──▶ pending_number
│
número conectado + concluir
▼
revoked ◀──cliente/parceiro/conta excluída── active
▲ │ ▲
└──────────────────────────────────────────────┘ │
Pro não pago ──▶ suspended
Pro pago ──────┘
statusO que significaA API key do parceiro
pending_accountSessão aberta, conta ainda não vinculadanão existe
pending_paymentConta vinculada, Pro não pagonão existe
pending_numberPro pago, WhatsApp não conectadonão existe
activeConcluídafunciona
suspendedO Pro do cliente não está pago402 connect_suspended
revokedEncerrada401 connect_revoked

Quem muda o estado:

  • Você: DELETE /partner/connections/{id} → revoked.
  • O cliente: "Apps conectados" no painel → revoked; pagar/deixar de pagar o Pro → active/suspended.
  • O bZapper: um varredor roda a cada 2 minutos (e no momento do rebaixamento de plano) e reconcilia: sem Pro pago → suspended; Pro pago de novo → active; conta ou projeto apagados → revoked. A checagem também acontece em toda chamada com a key, então suspensão e revogação valem na hora, sem esperar o varredor.
Revogação é definitiva

revoked é terminal. A conexão solta a conta (perde account_id/project_id), a key morre e qualquer rota de /connect/* passa a responder 409 connection_revoked, inclusive com um ingresso ainda dentro dos 30 minutos. Abrir uma sessão nova com o mesmo external_id recomeça no passo da conta, e como o e-mail já tem conta bZapper, exige o código enviado ao cliente. Você não reconecta sozinho.

6. Webhooks do parceiro​

Um endpoint só, cadastrado com você, recebe os eventos de todas as suas conexões.

Assinatura. X-Bzapper-Signature: sha256=<hex> = HMAC-SHA256 do corpo cru com o secret do webhook do parceiro (que não é o bz_partner_). Valide antes de dar parse. Cabeçalhos extras: X-Bzapper-Event-Id, X-Bzapper-Event-Type.

import hmac, hashlib
def valido(corpo: bytes, assinatura: str, secret: str) -> bool:
esperado = "sha256=" + hmac.new(secret.encode(), corpo, hashlib.sha256).hexdigest()
return hmac.compare_digest(esperado, assinatura)

Envelope. O padrão do bZapper mais o bloco connection:

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

Eventos de ciclo de vida​

EventoQuandopayload
connect.completedO cliente concluiu (Pro pago + WhatsApp conectado)status
connect.suspendedO Pro deixou de estar pagostatus
connect.resumedPagou e a conexão voltoustatus
connect.revokedEncerradastatus, revoked_by: customer | partner | account_deleted | project_deleted

Eventos de operação​

Das conexões active, os mesmos eventos de projeto do bZapper: message.received, message.sent, message.delivered, message.read, message.failed, instance.connected, instance.warming, instance.disconnected, instance.logged_out, instance.banned, contact.opted_out e group.*.

Vale para qualquer número do projeto da conexão — inclusive um que o cliente adicione sozinho pelo painel do bZapper: você recebe instance.connected e não precisa ficar reconciliando.

Não são repassados: qr_code e pairing_code. São o segredo do pareamento do aparelho do cliente.

Entrega​

  • Até 5 tentativas com backoff exponencial (1s, 2s, 4s, 8s).
  • Reenviamos em falha de transporte, 5xx e 429. Um 4xx (URL errada, assinatura recusada) não é retentado — corrija o endpoint.
  • Entrega pelo menos uma vez: deduplique por event_id.
  • Não há replay de histórico: você recebe o que acontece a partir da conexão. Para reconciliar estado, use GET /partner/connections.
  • Não é preciso cadastrar webhook na conta do cliente, e o seu canal não disputa com a regra de "um webhook por evento por projeto" que vale para os webhooks dele.

7. Componente embutido​

<script src="https://widget.bzapper.com.br/v1/connect.js"></script>

O bundle é autocontido (Preact + QR + i18n), monta em Shadow DOM e não usa iframe da própria tela. O formulário de cartão é do Stripe, montado num nó no light DOM projetado por <slot> — os iframes do Stripe não funcionam dentro de Shadow DOM.

const modal = BzapperConnect.open({
session: 'cs_…', // obrigatório
apiBase: 'https://api.bzapper.com.br',
locale: 'pt-BR',
onReady: ({ step, connectionId, status }) => {},
onStep: ({ step }) => {},
onComplete: ({ code, connectionId, externalId }) => {},
onClose: () => {},
onError: ({ code, message }) => {},
});
modal.close(); // fecha por código

Inline​

<bzapper-connect data-session="cs_…" data-api="https://api.bzapper.com.br" data-locale="pt-BR"></bzapper-connect>

Eventos DOM equivalentes, com bubbles e composed: bzapper:ready, bzapper:step, bzapper:complete, bzapper:close, bzapper:error. O detail é o mesmo objeto dos callbacks.

Passos (step)​

stepTelaSai quando
accountPergunta "Você já tem conta no bZapper?": Criar conta nova (confere os dados) ou Já tenho conta (vincula por e-mail)conta criada, ou código enviado
verify_emailCódigo de 6 dígitos (conta já existente)código confirmado
paymentAssinatura do Pro (cartão ou Pix)pagamento confirmado
numberQR code ou código de pareamentonúmero conectado
manageNúmeros conectados (conexão concluída)—
revokedConexão encerrada—

Idiomas: pt, en, es, it, de, fr. Sem locale, usa o lang do documento e depois o do navegador.

CSP do seu site​

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;

Endpoints que o componente consome​

Você não precisa chamá-los — estão aqui para depuração. Todos exigem X-Connect-Session e a origem cadastrada.

RotaPara quê
GET /connect/bootstrapEstado da tela: parceiro, cliente, passo, plano, números, chave publicável do Stripe
POST /connect/accountCria a conta (ou dispara o código, se o e-mail já tiver conta)
POST /connect/account/verifyConfirma o código de 6 dígitos
POST /connect/account/resendReenvia o código para o mesmo e-mail (1 por minuto)
POST /connect/account/link"Já tenho conta": {email} (vazio = o e-mail enviado pelo parceiro) → código para esse e-mail
POST /connect/account/link/cancelSai do passo do código (trocar de e-mail ou criar conta nova)
POST /connect/checkoutAssina o Pro num meio (card | pix) e devolve o client_secret
GET /connect/paymentConfirmação do pagamento (o componente consulta enquanto espera)
GET /connect/numbers · POST /connect/numbersLista e cria o número
POST /connect/numbers/{id}/connect?method=qr|codeGera QR ou código de pareamento
POST /connect/numbers/{id}/disconnectDesconecta
GET /connect/streamSSE do projeto: qr_code, pairing_code, instance.status
POST /connect/completeConclui e emite o code

8. Limites da API key entregue​

A key é role: agent, com os escopos do seu cadastro, presa ao projeto da conexão.

Alcança: /instances, /messages, /chats, /labels, /calls, /groups, /conversations, /presence, /pools, /campaigns, /usage, /advisories, POST /contacts/check, e as leituras GET /me, GET /me/entitlements, GET /me/subscription.

Não alcança (403 forbidden): /keys, /users, /projects, /account*, /me/plan*, /me/addons*, /me/invoices*, /billing*, /webhooks*, /me/widgets, /me/connections, /platform/*, /brand, /official.

Também fora: GET /stream e a base de contatos da conta (/contacts, /tags, /contact-groups, /suppressions, /blocklist). Ambos são por conta, não por projeto — e o SSE ainda carrega QR e código de pareamento. Você recebe os eventos pelo seu webhook, já filtrado por conexão.

Cerca de projeto: um número de outro projeto do mesmo cliente responde 404, e GET /instances?project_id=all devolve só o projeto da conexão.

9. Cobrança​

  • O canal de parceiros é só Pro. Não há Free: sem Pro pago não se conecta número nem se emite key.
  • Meios: cartão (liga a renovação automática, o cartão fica salvo) e Pix (só em BRL; as renovações chegam por link no e-mail). Boleto não entra no Connect. Se a conta Stripe do bZapper não tiver o Pix ativo, o componente passa a oferecer só cartão.
  • Quem paga é o cliente, direto ao bZapper: fatura e recibo saem no nome dele, e o cartão nunca passa pelo seu código nem pelo nosso.
  • Trocar de meio no meio do caminho (gerou Pix, voltou para o cartão) cancela a tentativa anterior. Se o pagamento anterior estiver em confirmação, a resposta é 409 payment_pending — espere alguns segundos e repita, em vez de cobrar duas vezes.
  • Queda de pagamento: suspended + connect.suspended. Reabrir o componente com o mesmo external_id leva o cliente direto à tela de pagamento, com o aviso. Pagou: active + connect.resumed, sem nova autorização.

10. Conta existente​

O componente sempre pergunta se o cliente já tem conta no bZapper. Quem já tem só conecta:

  1. Detecção na abertura. Se o e-mail que você mandou na sessão já tem conta, o GET /connect/bootstrap traz existing: "admin" (pode vincular) ou "member" (está numa conta, mas sem permissão), e o componente abre em Já tenho conta com "Encontramos sua conta" — o cliente não precisa descobrir isso clicando em "Criar conta".
  2. Outro e-mail. O cliente pode vincular por um e-mail diferente do que você mandou (POST /connect/account/link {email}). E-mail sem conta → 404 account_not_found.
  3. Enviamos um código de 6 dígitos ao e-mail da conta (step: "verify_email", com email_hint mascarado). "Trocar e-mail ou criar conta" descarta o código (POST /connect/account/link/cancel).
  4. O e-mail precisa ser administrador daquela conta (403 account_admin_required para um usuário comum). Sem isso, saber o e-mail de alguém bastaria para ganhar acesso à conta dela.
  5. Confirmado o código, a conexão usa o projeto padrão da conta. Se a conta já for Pro, o passo de pagamento é pulado; se já houver número conectado, o cliente só autoriza.

Tetos por e-mail alvo, na janela de 1 hora: 5 códigos enviados e 10 tentativas erradas (429 code_attempts_exceeded; o 6º envio responde 429 code_sends_exceeded). O código expira em 10 minutos e morre na 5ª tentativa errada da mesma conexão.

11. Tabela de erros​

CódigoHTTPOndeSignificado
partner_unauthorized401/partner/*Secret ausente, errado, rotacionado ou enviado fora do header
partner_inactive403todasIntegração desativada pelo bZapper
external_id_required400criar sessãoFalta external_id (ou passa de 200 caracteres)
customer_email_required400criar sessãoE-mail ausente ou inválido
customer_name_required400criar sessãoFalta name ou company
connect_session_required401componenteSem X-Connect-Session
invalid_connect_session401componenteIngresso inválido
connect_session_expired401componenteIngresso passou dos 30 min → abra outro
origin_not_allowed403componenteDomínio da página fora das origens cadastradas
connection_revoked409componenteO cliente encerrou: abra uma sessão nova (recomeça no passo da conta)
account_required409componentePasso pedido antes de vincular a conta
account_admin_required403componenteO e-mail tem conta bZapper, mas não é admin dela
invalid_code400componenteCódigo de e-mail errado ou expirado
code_attempts_exceeded429componenteTetos por e-mail alvo estourados
code_sends_exceeded429componenteCódigos enviados demais para aquele e-mail na última hora
account_not_found404componente"Já tenho conta" com um e-mail que não tem conta no bZapper
payment_required402componenteNúmero ou conclusão antes do Pro pago
payment_pending409componenteTentativa anterior em confirmação (Pix/3DS)
already_paid409componenteO plano já está pago
method_unavailable422componenteMeio não serve (ex.: Pix fora do BRL)
payment_unavailable502componenteO meio falhou no Stripe (ex.: Pix não ativo)
number_required409componenteConcluir sem número conectado
quota_exceeded402componenteLimite de números do plano
invalid_code (troca)400/partner/connect/exchangeCode errado, vencido, já usado ou de outro parceiro
connection_not_found404/partner/connections/*Não é sua
connection_not_active409rotate-keyConexão não concluída ou encerrada
connect_suspended402API keyPro do cliente não pago
connect_revoked401API keyConexão encerrada
forbidden403API keyRota fora do alcance da key de parceiro

12. Operação (bZapper)​

O cadastro de parceiros fica em Plataforma → Parceiros, no painel, e exige administrador do tenant da plataforma.

  • Criar: nome, slug, logo (upload do arquivo: PNG, JPEG, WebP ou SVG, até 2 MB), origens permitidas, URL do webhook e escopos da key. O secret do parceiro e o secret do webhook aparecem uma única vez.
  • Trocar a logo: POST /platform/partners/{id}/logo (multipart, campo file) — é o que o botão do painel faz. A imagem vai para a CDN do bZapper e o cadastro guarda a URL.
  • Rotacionar: Novo secret e Novo secret do webhook invalidam o anterior na hora.
  • Desativar: desliga Ativo → as sessões param de abrir e todas as keys daquele parceiro passam a responder 401 connect_revoked. É o botão de emergência.
  • Conexões: a lista mostra external_id, cliente, status e data de cada conexão.

Do lado do cliente, Apps conectados mostra os parceiros ligados à conta e permite desconectar (só administradores da conta).

13. Limites conhecidos​

  • Origens: comparação exata, sem curinga. Cada domínio novo precisa ser cadastrado.
  • Idempotência no envio: client_reference é eco, não chave única. Um retry seu duplica a mensagem — guarde o id devolvido e reenvie só o que falhou.
  • Sem replay de eventos antigos no canal do parceiro.
  • Projeto padrão em conta existente: a conexão usa o projeto padrão da conta, não um projeto dedicado.
  • Dois parceiros na mesma conta e projeto recebem os mesmos eventos daquele projeto.