Pular para o conteúdo principal

SDKs oficiais

Bibliotecas oficiais do bZapper para integrar em minutos, na sua linguagem. Todas cobrem o mesmo conjunto de operações da API: os 13 tipos de mensagem (texto, imagem, vídeo, documento, áudio, sticker, localização, contato, enquete, reação, botões, lista e OTP), números/instâncias, API keys, uso, e as funções avançadas — grupos, presença, conversas e contatos.

São 7 linguagens — Node/TypeScript, Python, PHP, .NET (C#), Java, Go e Ruby — e todas seguem o padrão Berni Software, o mesmo dos SDKs do bFocus:

  • toda operação da API tem um método (159), com o nome do operationId da spec;
  • novas tentativas automáticas e seguras, porque toda escrita leva uma Idempotency-Key que a API honra (veja Erros, novas tentativas e idempotência);
  • erros tipados com code estável e requestId para o suporte;
  • verificação de assinatura dos webhooks (HMAC-SHA256) e o cliente do bZapper Connect;
  • uma suíte de conformidade única que as 7 rodam — um endpoint sem método quebra o build;
  • zero dependências de runtime (só o Java traz o Jackson).
Disponíveis nos registros oficiais 🎉

Instale com um comando — sem clonar nada. Node, Python, PHP, .NET, Java, Go e Ruby já publicados (npm, PyPI, Packagist, NuGet, Maven Central, Go modules e RubyGems).

O essencial: você só precisa da sua API key​

O SDK já aponta para a API de produção (https://api.bzapper.com.br). Você não passa URL nenhuma — basta a sua API key (bz_live_..., gerada no painel em Chaves de API). Isso é tudo.

A URL da API é opcional e serve só para desenvolvimento (http://localhost:8080) ou self-host. Em produção, não informe nada.

Instalação​

LinguagemInstalação
Node / TypeScriptnpm install @bzapper/client
Pythonpip install bzapper
PHPcomposer require bzapper/bzapper
Gogo get github.com/bernisoftware/bzapper-go@latest
.NET (C#)dotnet add package Bzapper
Java (Maven)veja o bloco <dependency> abaixo
Rubygem install bzapper (ou gem "bzapper" no Gemfile)

Para Node, Python, PHP, .NET, Go e Ruby o comando acima já baixa a versão mais recente e todas as dependências — você não adiciona mais nada.

Java — dependência (Maven / Gradle)​

Adicione apenas o artefato do SDK. A única dependência de runtime (Jackson, para JSON) vem transitivamente — você não precisa declarar mais nada.

Maven (pom.xml):

<dependency>
<groupId>br.com.bernisoftware</groupId>
<artifactId>bzapper</artifactId>
<version>0.8.1</version>
</dependency>

Gradle (build.gradle.kts):

implementation("br.com.bernisoftware:bzapper:0.8.1")

Requer Java 17+. Nada de Gson, OkHttp ou qualquer outra lib manual — o SDK usa o java.net.http.HttpClient do próprio JDK e puxa o Jackson sozinho.

Início rápido​

Instalou? Então é só passar a API key e enviar. Nenhuma URL.

Node / TypeScript​

import { Bzapper } from '@bzapper/client';

const bz = new Bzapper({ apiKey: 'bz_live_...' });
await bz.sendText({ to: '+5511999999999', body: 'Olá do bZapper! 👋' });

Python​

from bzapper import Client

bz = Client("bz_live_...")
bz.send_text(to="+5511999999999", body="Olá do bZapper! 👋")

PHP​

use Bzapper\Client;

$bz = new Client("bz_live_...");
$bz->sendText("+5511999999999", "Olá do bZapper! 👋");

Go​

bz := bzapper.NewClient("bz_live_...")
bz.SendText(context.Background(), bzapper.SendTextParams{
SendBase: bzapper.SendBase{To: "+5511999999999"},
Body: "Olá do bZapper! 👋",
})

Java​

import com.bernisoftware.bzapper.BzapperClient;
import com.bernisoftware.bzapper.model.SendOptions;

var bz = new BzapperClient("bz_live_...");
bz.sendText(SendOptions.to("+5511999999999"), "Olá do bZapper! 👋");

.NET (C#)​

using Bzapper;

using var bz = new BzapperClient("bz_live_...");
var msg = await bz.SendTextAsync(new SendText { To = "+5511999999999", Body = "Olá do bZapper! 👋" });

Ruby​

require "bzapper"

client = Bzapper::Client.new("bz_live_...")
client.messages.send_text(to: "+5511999999999", body: "Olá do bZapper! 👋")

Apontar para dev/self-host (opcional)​

Só se você não estiver usando a produção:

new Bzapper({ apiKey: 'bz_live_...', baseUrl: 'http://localhost:8080' }); // Node
Client("bz_live_...", "http://localhost:8080")  # Python
new Client("bz_live_...", "http://localhost:8080"); // PHP
bzapper.NewClient("bz_live_...", bzapper.WithBaseURL("http://localhost:8080")) // Go
new BzapperClient("http://localhost:8080", "bz_live_..."); // Java
new BzapperClient("bz_live_...", new BzapperClientOptions { BaseUrl = "http://localhost:8080" }); // .NET
Bzapper::Client.new("bz_live_...", base_url: "http://localhost:8080") # Ruby

Dica: explore e teste tudo no Playground dentro do painel (admin), com envio real e exemplos de código prontos em cada linguagem.

Presença em grupo​

Mostrar “digitando…” num grupo é só apontar a presença para o JID do grupo:

bz.presence_chat(instance_id=inst, to="[email protected]", state="typing")

Webhooks — receber e processar eventos​

Os SDKs recebem o payload do webhook e processam pra você: verificam a assinatura HMAC (X-Bzapper-Signature), transformam o envelope num evento tipado e roteiam pra um handler por tipo. Cada SDK também faz o CRUD dos webhooks (createWebhook/listWebhooks/…). Eventos: message.{received,sent,delivered,read,failed}, instance.{connected,disconnected,banned,logged_out,warming,status}, group.{joined,participant_added,participant_removed,participant_promoted,participant_demoted,subject_changed,description_changed}.

Python​

from bzapper.webhooks import Webhooks

hooks = Webhooks(secret="whsec_...") # secret devolvido pelo create_webhook

@hooks.on("message.received")
def _(event):
print(event.sender.name, event.payload["body"])

# no seu endpoint — corpo CRU + header. Lança SignatureError se inválido.
hooks.handle(raw_body=request.get_data(), signature=request.headers["X-Bzapper-Signature"])

Node / TypeScript​

import { Webhooks } from '@bzapper/client';

const hooks = new Webhooks('whsec_...');
hooks.on('message.received', (e) => console.log(e.sender?.name, e.payload.body));

// Express: use express.raw() e o middleware pronto
app.post('/webhooks', express.raw({ type: '*/*' }), hooks.middleware());

Go​

rx := bzapper.NewWebhookReceiver("whsec_...").
On("message.received", func(e *bzapper.WebhookEvent) { /* ... */ })
http.Handle("/webhooks", rx) // é um http.Handler: verifica + roteia sozinho

.NET (C#)​

// body = bytes CRUS da requisição; assinatura inválida → WebhookSignatureException
var ev = Webhooks.ConstructEvent(secret, body, req.Headers[Webhooks.SignatureHeader]);
if (ev.Type == "message.received") { /* ev.Sender, ev.Payload… */ }

Ruby​

router = Bzapper::Webhook::Router.new("whsec_...")
router.on("message.received") { |ev| puts ev.sender&.dig("name"), ev.payload["body"] }
router.handle(request.body.read, request.get_header("HTTP_X_BZAPPER_SIGNATURE")) # verifica + roteia; SignatureError se inválido

PHP (new Bzapper\Webhooks($secret)) e Java (new Webhooks(secret)) seguem o mesmo padrão: on(tipo, handler) + handle(corpoCru, assinatura). Use o event_id para idempotência (a API pode reentregar). O verify é timing-safe; sempre passe o corpo CRU (não o JSON re-serializado).

bZapper Connect (softwares parceiros)​

Se você é um software parceiro e quer que os seus clientes contratem o bZapper e conectem o WhatsApp dentro do seu produto, use o cliente de parceiro. Ele autentica com o secret bz_partner_… e só existe no backend — nunca no navegador.

O passo a passo está no guia do Connect e o detalhe de cada campo na referência.

Construtor​

SDKConstrutor
Nodenew BzapperPartner({ partnerSecret, baseUrl?, locale?, timeout? }) ou createPartnerClient({ … })
PythonPartnerClient(partner_secret, base_url=None, locale=None, timeout=30)
Gobzapper.NewPartnerClient(secret, opts...) (produção) ou bzapper.NewPartner(baseURL, secret, opts...)
PHPnew Bzapper\PartnerClient($partnerSecret, $baseUrl = null, $opts = [])
Javanew BzapperPartner(partnerSecret) ou BzapperPartner.builder(partnerSecret)…build()
.NETnew PartnerClient(partnerKey) ou new PartnerClient(partnerKey, new BzapperClientOptions { … })
RubyBzapper::PartnerClient.new(partner_key, base_url:, timeout:, max_retries:, locale:) (todos opcionais, exceto a chave)

Métodos do parceiro​

O que fazNodePythonGoPHPJava.NETRuby
Quem sou eume()me()Me(ctx)me()me()GetPartnerMeAsync()get_partner_me
Abrir sessãocreateConnectSession({ external_id, customer, locale? })create_connect_session(external_id, customer, locale=None)CreateConnectSession(ctx, CreateConnectSessionParams{…})createConnectSession($externalId, $customer, $locale = null)createConnectSession(externalId, customer, locale)CreateConnectSessionAsync(new CreateConnectSessionRequest { ExternalId, Customer, Locale })create_connect_session(external_id:, customer:, locale:)
Trocar o codeexchangeCode(code)exchange_code(code)ExchangeCode(ctx, code)exchangeCode($code)exchangeCode(code)ExchangeConnectCodeAsync(new ExchangeConnectCodeRequest { Code })exchange_connect_code(code:)
Listar conexõeslistConnections({ external_id?, status? })list_connections(external_id=None, status=None)ListConnections(ctx, ListConnectionsParams{…})listConnections($externalId = null, $status = null)listConnections(externalId, status)ListPartnerConnectionsAsync(externalId, status)list_partner_connections(external_id:, status:)
Uma conexãogetConnection(id)get_connection(id)GetConnection(ctx, id)getConnection($id)getConnection(id)GetPartnerConnectionAsync(id)get_partner_connection(id)
Nova keyrotateConnectionKey(id)rotate_connection_key(id)RotateConnectionKey(ctx, id)rotateConnectionKey($id)rotateConnectionKey(id)RotatePartnerConnectionKeyAsync(id)rotate_partner_connection_key(id)
EncerrarrevokeConnection(id)revoke_connection(id)RevokeConnection(ctx, id)revokeConnection($id)revokeConnection(id)RevokePartnerConnectionAsync(id)revoke_partner_connection(id)

Formato do retorno das listas: Node, Python, PHP, .NET e Ruby devolvem o objeto inteiro, com o envelope { data: [...] } (em .NET, ListPartnerConnectionsResult.Data; em Ruby, resultado["data"]), como os demais métodos de listagem desses SDKs; Go e Java devolvem a lista já desembrulhada ([]PartnerConnection / List<PartnerConnection>).

Métodos do cliente (com a API key normal)​

listConnectedApps() e revokeConnectedApp(id) — em Python, list_connected_apps() e revoke_connected_app(connection_id); em Go, ListConnectedApps(ctx) e RevokeConnectedApp(ctx, id); em .NET, ListConnectedAppsAsync() e RevokeConnectedAppAsync(id); em Ruby, client.connect.list_connected_apps e client.connect.revoke_connected_app(id). São os "Apps conectados" da conta: use para mostrar ao seu cliente quem está ligado e para desconectar.

Exemplos​

import { BzapperPartner, createClient } from '@bzapper/client';

const partner = new BzapperPartner({ partnerSecret: process.env.BZAPPER_PARTNER_SECRET! });

// 1. o seu backend abre a sessão
const { session_token } = await partner.createConnectSession({
external_id: empresa.id,
customer: { name: empresa.responsavel, email: empresa.email, company: empresa.nome, country: 'BR' },
});

// 2. o front abre o componente com esse token e devolve o `code`
// 3. o seu backend troca o code pela key do cliente
const conexao = await partner.exchangeCode(code);
await salvarKey(empresa.id, conexao.api_key);

// 4. dali em diante, é o cliente normal com a key dele
const bz = createClient({ apiKey: await lerKey(empresa.id) });
await bz.sendText({ to: '+5511999990000', body: 'Seu pedido saiu para entrega' });
from bzapper import PartnerClient, Bzapper, BzapperError

parceiro = PartnerClient(os.environ["BZAPPER_PARTNER_SECRET"])

sessao = parceiro.create_connect_session(
external_id=str(empresa.id),
customer={"name": empresa.responsavel, "email": empresa.email,
"company": empresa.nome, "country": "BR"},
)
# … devolva sessao["session_token"] ao front; depois do onComplete:
conexao = parceiro.exchange_code(code)
salvar_key(empresa.id, conexao["api_key"])

try:
Bzapper(api_key=ler_key(empresa.id)).send_text(to="+5511999990000", body="Olá")
except BzapperError as e:
if e.code == "connect_suspended": # 402: Pro do cliente não pago
avisar_para_regularizar(empresa)
elif e.code == "connect_revoked": # 401: o cliente desconectou você
apagar_key(empresa.id)
parceiro := bzapper.NewPartnerClient(os.Getenv("BZAPPER_PARTNER_SECRET"))

sessao, err := parceiro.CreateConnectSession(ctx, bzapper.CreateConnectSessionParams{
ExternalID: empresa.ID,
Customer: bzapper.ConnectCustomer{Name: empresa.Responsavel, Email: empresa.Email, Company: empresa.Nome, Country: "BR"},
})
// … depois do onComplete:
conexao, err := parceiro.ExchangeCode(ctx, code)
salvarKey(empresa.ID, conexao.APIKey)

Webhooks do parceiro​

Os eventos de todas as suas conexões chegam num endpoint só, com a mesma verificação de assinatura que os SDKs já expõem (Webhooks/verify), usando o secret do webhook do parceiro. O envelope traz um bloco connection a mais:

  • Node: event.connection (com isConnectEvent() e CONNECT_EVENT_TYPES).
  • Python: event.connection (ConnectionRef) e CONNECT_EVENT_TYPES.
  • Go: event.Connection (*WebhookConnection) e ConnectEventTypes.
  • PHP: a chave connection do evento e as constantes Webhooks::EVENT_CONNECT_*.
  • Java: event.connection() (WebhookConnection) e Webhooks.CONNECT_EVENT_TYPES.
  • .NET: ev.Connection (WebhookConnection).
  • Ruby: event.connection (um Hash) e Bzapper::Webhook::CONNECT_EVENT_TYPES.

Eventos de ciclo de vida: connect.completed, connect.suspended, connect.resumed, connect.revoked. Os eventos de operação (message.*, instance.*) chegam pelo mesmo canal, para as conexões ativas.

Códigos que a sua integração precisa tratar​

CódigoHTTPO que fazer
connect_suspended402O Pro do cliente não está pago: mostre um aviso e reabra o componente no mesmo external_id
connect_revoked401O cliente desconectou você: apague a key guardada
payment_pending409Pagamento anterior em confirmação: espere e repita
account_admin_required403O e-mail já tem conta bZapper, mas não é admin dela
code_attempts_exceeded429Tetos de código por e-mail alvo estourados (5 envios / 10 tentativas por hora)

Erros, novas tentativas e idempotência​

Os 7 SDKs se comportam igual aqui — é o contrato do padrão Berni Software:

  • Erros tipados. Toda resposta fora de 2xx lança um erro da família BzapperError (BzapperException em PHP, .NET e Java; *bzapper.Error em Go; Bzapper::Error em Ruby), com uma subclasse por status: AuthenticationError (401), PermissionDeniedError (403), NotFoundError (404), ConflictError (409), ValidationError (400/422), RateLimitError (429, com retryAfter), ServerError (5xx) e NetworkError (falha de rede/timeout, status 0, code NETWORK_ERROR). Em PHP, .NET e Java os nomes terminam em Exception; em Go são sentinelas para errors.Is (ErrNotFound, ErrRateLimit…).
  • code estável. Use sempre o code na sua lógica — nunca o texto, que vem traduzido (locale) e pode mudar.
  • requestId em todo erro. Cada chamada leva um X-Request-Id; o erro traz esse id (request_id/requestId/RequestId/getRequestId()). Informe-o ao suporte: é por ele que achamos a sua chamada nos logs.
  • Novas tentativas automáticas (padrão 2, configurável; 0 desliga) só em erro de rede/timeout, 429, 502, 503 e 504 — um 500 ou 4xx volta na hora. Respeitam o Retry-After (teto 60 s) ou fazem backoff exponencial com jitter.
  • Seguras por idempotência. Toda escrita (POST/PUT/PATCH/DELETE) leva uma Idempotency-Key, a mesma em todas as tentativas, e a API honra essa chave em todas as escritas: numa repetição ela devolve a resposta original (Idempotent-Replayed: true, por 24 h) em vez de executar de novo. Ou seja: a mensagem não sai duas vezes.

Python​

from bzapper import BzapperError, RateLimitError

try:
bz.send_text(to="+5511999999999", body="Olá!")
except RateLimitError as e:
time.sleep(e.retry_after or 1)
except BzapperError as e:
print(e.code, e.status_code, e.request_id) # ex.: "not_connected", 409, "a1b2…"

Node / TypeScript​

import { BzapperError, RateLimitError } from '@bzapper/client';

try {
await bz.sendText({ to: '+5511999999999', body: 'Olá!' });
} catch (err) {
if (err instanceof RateLimitError) console.error(`aguarde ${err.retryAfter}s`);
else if (err instanceof BzapperError) console.error(err.code, err.status, err.requestId);
else throw err;
}

PHP​

use Bzapper\BzapperException;
use Bzapper\RateLimitException;

try {
$bz->sendText('+5511999999999', 'Olá!');
} catch (RateLimitException $e) {
sleep($e->getRetryAfter() ?? 1);
} catch (BzapperException $e) {
echo $e->getErrorCode(), $e->getStatusCode(), $e->getRequestId(); // use o código, não a mensagem
}

.NET (C#)​

try
{
await bz.SendTextAsync(new SendText { To = "+5511999999999", Body = "Olá!" });
}
catch (RateLimitException e)
{
await Task.Delay(e.RetryAfter ?? TimeSpan.FromSeconds(5));
}
catch (BzapperException e)
{
logger.LogError("bZapper {Code} (HTTP {Status}, request_id {RequestId})", e.Code, e.Status, e.RequestId);
}

Java​

try {
client.sendText(SendOptions.to("+5511999999999"), "Olá!");
} catch (RateLimitException e) {
retryLater(e.getRetryAfter()); // Duration
} catch (BzapperException e) {
log.error("bZapper {} ({}) request_id={}", e.getCode(), e.getStatusCode(), e.getRequestId());
}

Go​

_, err := client.SendText(ctx, bzapper.SendTextParams{SendBase: bzapper.SendBase{To: "+5511999999999"}, Body: "Olá!"})
var e *bzapper.Error
if errors.Is(err, bzapper.ErrRateLimit) && errors.As(err, &e) {
time.Sleep(e.RetryAfter)
} else if errors.As(err, &e) {
log.Printf("%s (http %d) request_id=%s", e.Code, e.StatusCode, e.RequestID)
}

Ruby​

begin
client.messages.send_text(to: "+5511999999999", body: "Olá!")
rescue Bzapper::RateLimitError => e
sleep(e.retry_after || 1)
rescue Bzapper::Error => e
warn "#{e.code} (HTTP #{e.status}) request_id=#{e.request_id}"
end

Para deduplicar também reexecuções do seu código (um job que roda duas vezes), passe a sua própria chave — por exemplo, o id do pedido:

bz.send_text(to="+5511999999999", body="Pedido 4471 confirmado", idempotency_key="pedido-4471")               # Python
await bz.sendText({ to: '+5511999999999', body: 'Pedido 4471 confirmado' }, { idempotencyKey: 'pedido-4471' }); // Node
$bz->sendText('+5511999999999', 'Pedido 4471 confirmado', ['idempotency_key' => 'pedido-4471']);          // PHP
await bz.SendTextAsync(new SendText { To = "+5511999999999", Body = "Pedido 4471 confirmado" },
new RequestOptions { IdempotencyKey = "pedido-4471" }); // .NET
client.sendText(SendOptions.to("+5511999999999").withIdempotencyKey("pedido-4471"), "Pedido 4471 confirmado");  // Java
ctx := bzapper.ContextWithIdempotencyKey(ctx, "pedido-4471") // Go: vale para qualquer escrita
client.messages.send_text(to: "+5511999999999", body: "Pedido 4471 confirmado", idempotency_key: "pedido-4471") # Ruby

Fixe a versão exata do SDK (ex.: bzapper==0.8.1, "@bzapper/client": "0.8.1", gem "bzapper", "0.8.1"): cada release declara se muda a superfície pública ou se é só aditiva, então atualizar é uma decisão sua.

Cada SDK tem um README completo (no repositório do pacote) com exemplos de cada tipo de mensagem, grupos, presença, conversas e erros.