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
operationIdda spec; - novas tentativas automáticas e seguras, porque toda escrita leva uma
Idempotency-Keyque a API honra (veja Erros, novas tentativas e idempotência); - erros tipados com
codeestável erequestIdpara 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).
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
| Linguagem | Instalação |
|---|---|
| Node / TypeScript | npm install @bzapper/client |
| Python | pip install bzapper |
| PHP | composer require bzapper/bzapper |
| Go | go get github.com/bernisoftware/bzapper-go@latest |
| .NET (C#) | dotnet add package Bzapper |
| Java (Maven) | veja o bloco <dependency> abaixo |
| Ruby | gem 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
| SDK | Construtor |
|---|---|
| Node | new BzapperPartner({ partnerSecret, baseUrl?, locale?, timeout? }) ou createPartnerClient({ … }) |
| Python | PartnerClient(partner_secret, base_url=None, locale=None, timeout=30) |
| Go | bzapper.NewPartnerClient(secret, opts...) (produção) ou bzapper.NewPartner(baseURL, secret, opts...) |
| PHP | new Bzapper\PartnerClient($partnerSecret, $baseUrl = null, $opts = []) |
| Java | new BzapperPartner(partnerSecret) ou BzapperPartner.builder(partnerSecret)…build() |
| .NET | new PartnerClient(partnerKey) ou new PartnerClient(partnerKey, new BzapperClientOptions { … }) |
| Ruby | Bzapper::PartnerClient.new(partner_key, base_url:, timeout:, max_retries:, locale:) (todos opcionais, exceto a chave) |
Métodos do parceiro
| O que faz | Node | Python | Go | PHP | Java | .NET | Ruby |
|---|---|---|---|---|---|---|---|
| Quem sou eu | me() | me() | Me(ctx) | me() | me() | GetPartnerMeAsync() | get_partner_me |
| Abrir sessão | createConnectSession({ 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 code | exchangeCode(code) | exchange_code(code) | ExchangeCode(ctx, code) | exchangeCode($code) | exchangeCode(code) | ExchangeConnectCodeAsync(new ExchangeConnectCodeRequest { Code }) | exchange_connect_code(code:) |
| Listar conexões | listConnections({ 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ão | getConnection(id) | get_connection(id) | GetConnection(ctx, id) | getConnection($id) | getConnection(id) | GetPartnerConnectionAsync(id) | get_partner_connection(id) |
| Nova key | rotateConnectionKey(id) | rotate_connection_key(id) | RotateConnectionKey(ctx, id) | rotateConnectionKey($id) | rotateConnectionKey(id) | RotatePartnerConnectionKeyAsync(id) | rotate_partner_connection_key(id) |
| Encerrar | revokeConnection(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(comisConnectEvent()eCONNECT_EVENT_TYPES). - Python:
event.connection(ConnectionRef) eCONNECT_EVENT_TYPES. - Go:
event.Connection(*WebhookConnection) eConnectEventTypes. - PHP: a chave
connectiondo evento e as constantesWebhooks::EVENT_CONNECT_*. - Java:
event.connection()(WebhookConnection) eWebhooks.CONNECT_EVENT_TYPES. - .NET:
ev.Connection(WebhookConnection). - Ruby:
event.connection(umHash) eBzapper::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ódigo | HTTP | O que fazer |
|---|---|---|
connect_suspended | 402 | O Pro do cliente não está pago: mostre um aviso e reabra o componente no mesmo external_id |
connect_revoked | 401 | O cliente desconectou você: apague a key guardada |
payment_pending | 409 | Pagamento anterior em confirmação: espere e repita |
account_admin_required | 403 | O e-mail já tem conta bZapper, mas não é admin dela |
code_attempts_exceeded | 429 | Tetos 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(BzapperExceptionem PHP, .NET e Java;*bzapper.Errorem Go;Bzapper::Errorem Ruby), com uma subclasse por status:AuthenticationError(401),PermissionDeniedError(403),NotFoundError(404),ConflictError(409),ValidationError(400/422),RateLimitError(429, comretryAfter),ServerError(5xx) eNetworkError(falha de rede/timeout, status0,codeNETWORK_ERROR). Em PHP, .NET e Java os nomes terminam emException; em Go são sentinelas paraerrors.Is(ErrNotFound,ErrRateLimit…). codeestável. Use sempre ocodena sua lógica — nunca o texto, que vem traduzido (locale) e pode mudar.requestIdem todo erro. Cada chamada leva umX-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;
0desliga) só em erro de rede/timeout,429,502,503e504— um500ou4xxvolta na hora. Respeitam oRetry-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.