@bzapper/ 客户端
SDK的官职 b 泽珀 第11段 节点/节点-网关-WhatsApp 多用户:cenecte números, envie mensages, gerencie instâncias, API keys e companyhe o uso.
- 无害环境管理+类型
- 运行时间零度 (苏萨)
fetch节点18+) - 埃尔罗斯·皮卡多斯(Erros tipados)
BzapperError+ 子类 por 状态) comcode报告erequestId - 诺瓦斯自制帐篷
Idempotency-Key(原始内容存档于2013-10-12) (英语). em toda escrita). – paderão Berni Software - Um Método para cada operação pública da API (159), pestado contra a suíte de conferidade (禁止遵守)
英斯特拉桑
npm i @bzapper/client
重新定义 节点18+ (fetch nativo) (英语).
修复一个 versão exata 没有 package.json (单位:千美元)"@bzapper/client": "0.16.0"中标 ^: 卡达释放
(原始内容存档于2013-10-12). declara nas notas se muda a superfície pública ou se é só aditiva.
哈罗世界
import { Bzapper } from "@bzapper/client";
const bz = new Bzapper({ apiKey: "bz_live_..." });
await bz.sendText({ to: "+5511999999999", body: "Olá!" });
欧 baseUrl tem 默认https://api.bzapper.com.br页:1 逻辑 —— 说
通知 def/self-host: new Bzapper({ apiKey: "bz_live_...", baseUrl: "http://localhost:8080" })。 。 。 .
Também há uma 工厂:
import { createClient } from "@bzapper/client";
const bz = createClient({ apiKey: "bz_live_..." });
厄立特里亚
Crie a API 密钥无 pael do bZapper (配置- API 密钥{\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}你该死
bz.createKey(...)键bz_live_...低调一个嗯 专业 (números,收件箱e 数据)
伊索拉多斯·普罗耶托(英语:isolados por projeto); para agir em outro projeto da conta, passe projectId (没有标题)
X-Project-IdNunca exponha 一把钥匙,不是无名小卒.
配置
new Bzapper({
apiKey: "bz_live_...",
locale: "pt-BR", // opcional → header Accept-Language (mensagens de erro traduzidas)
timeout: 30_000, // opcional, ms por tentativa (default 30000)
maxRetries: 2, // opcional, novas tentativas além da primeira (default 2; 0 desliga)
projectId: "…", // opcional → header X-Project-Id
baseUrl: "http://localhost:8080", // opcional, só em dev/self-host
});
托达·恩维亚 Authorization: Bearer <apiKey>, (中文(简体) ), Accept: application/json, (中文(简体) ),
X-Bzapper-Client: bzapper-node/<versão> (音乐) User-Agent), um (中文(简体) ) X-Request-Id 波尔
沙玛达, Idempotency-Key 纳斯·埃斯克丽塔斯, Content-Type: application/json (夸多哈科波)e
Accept-Language编号 :X-Project-Id (原始内容存档于2017-09-27). quando configurados. 托多·梅多·阿西塔(英语: 乌尔蒂莫争论
逻辑 { idempotencyKey?, timeout?, maxRetries?, signal? }。 。 。 .
门生
活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活SendBase: to (obrigatório, E.164 ou JID), (中文(简体) ), instance_id?, (中文(简体) ), pool_id?, (中文(简体) ), quoted_message_id?, (中文(简体) ), quoted_participant? (c) 国家妇女发展委员会, client_reference?, (中文(简体) ), mentions? (JIDs ou Telefones) (美国英语). 托多斯重毁 { message_id, status, client_reference? }。 。 。 .
重试seguro:通过 { idempotencyKey } 科莫 2o artigo de qualquer envio. 中国植物物种信息数据库. 重回原地 重回原地 idempotency_in_progress se a a amda roda; 422 单相机 idempotency_key_reused {\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}我...
await bz.sendText({ to: "+5511999999999", body: "Pedido #42 confirmado" }, { idempotencyKey: "pedido-42" });
文本
await bz.sendText({ to: "+5511999999999", body: "Olá!" });
图像
await bz.sendImage({
to: "+5511999999999",
media: { url: "https://example.com/foto.jpg", caption: "Veja isto" },
});
维多
await bz.sendVideo({
to: "+5511999999999",
media: { url: "https://example.com/video.mp4", caption: "Demo" },
});
文档
await bz.sendDocument({
to: "+5511999999999",
media: {
url: "https://example.com/contrato.pdf",
filename: "contrato.pdf",
mimetype: "application/pdf",
},
});
阿乌迪奥(法语:Nota de voz com) ptt(中文(简体) )
await bz.sendAudio({
to: "+5511999999999",
media: { url: "https://example.com/audio.ogg", ptt: true },
});
贴纸
await bz.sendSticker({
to: "+5511999999999",
media: { url: "https://example.com/sticker.webp" },
});
弥陀曰: 使用情况
url尔base64{\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}那又怎样.
当地
await bz.sendLocation({
to: "+5511999999999",
latitude: -23.5613,
longitude: -46.6565,
name: "Av. Paulista",
address: "São Paulo, SP",
});
康塔托( vCard) Name
await bz.sendContact({
to: "+5511999999999",
contact_name: "Suporte bZapper",
// telefone DO CONTATO do cartão, não o destino
contact_phone: "+5511977776666",
});
组合
await bz.sendPoll({
to: "+5511999999999",
name: "Qual seu plano?",
options: ["Free", "Pro", "Enterprise"],
selectable_count: 1,
});
目标
quoted_message_id {\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}请说.
await bz.sendReaction({
to: "+5511999999999",
quoted_message_id: "WA_MESSAGE_ID",
emoji: "👍",
});
瓶装
await bz.sendButtons({
to: "+5511999999999",
body: "Confirma o pedido?",
footer: "bZapper",
buttons: [
{ id: "yes", title: "Sim" },
{ id: "no", title: "Não" },
],
});
名单a
await bz.sendList({
to: "+5511999999999",
body: "Escolha uma opção",
button_text: "Abrir menu",
footer: "bZapper",
sections: [
{
title: "Planos",
rows: [
{ id: "pro", title: "Pro", description: "R$ 99/mês" },
{ id: "ent", title: "Enterprise", description: "Sob consulta" },
],
},
],
});
卡维特·佩特里奥: 单数 圣孔菲亚韦斯 无"什么"(por em grupos). API( API ) 启动 {\fn华文仿宋\fs16\1chd1d1d1}嗯,嗯.. 文本数字菜单 {\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}等等 {\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}等等...
Editar, apagar, encaminhar, marcar lido e agendaler 互联网档案馆的存檔,存档日期2013-12-21
await bz.editMessage(msg.message_id, { text: "Texto corrigido" });
await bz.revokeMessage(msg.message_id, { for_everyone: true });
await bz.forwardMessage({ instance_id: inst.id, to: "+5511988887777", from_chat: jid, wa_message_id: "3EB0…" });
await bz.markRead("3EB0…", { instance_id: inst.id, chat: jid });
// Qualquer envio aceita `scheduled_at` (RFC3339).
const s = await bz.sendText({ to: "+5511999999999", body: "Lembrete", scheduled_at: "2026-10-01T12:00:00Z" });
const { data: pending } = await bz.listScheduled({ limit: 50 });
await bz.cancelScheduled(s.scheduled_id!);
因斯通克(números)
const { data } = await bz.listInstances();
const inst = await bz.createInstance({
phone: "+5511999999999",
nickname: "Suporte",
proxy_url: "http://user:[email protected]:8080", // opcional
});
await bz.getInstance(inst.id);
const up = await bz.getInstanceUptime(inst.id); // up.windows["24h"] / ["7d"] / ["30d"] (null = sem histórico ainda), up.events
// Conexão por QR (default) ou código de pareamento.
const qr = await bz.connectInstance(inst.id, "qr");
console.log(qr.qr_code);
const code = await bz.connectInstance(inst.id, "code");
console.log(code.pair_code);
await bz.disconnectInstance(inst.id);
await bz.logoutInstance(inst.id); // exige novo QR
await bz.clearInstanceSession(inst.id); // pareamento travado: apaga a credencial do dispositivo
// Arquivar mantém o histórico; `archived: "1"` lista os arquivados.
await bz.archiveInstance(inst.id);
const archived = await bz.listInstances({ archived: "1" });
await bz.unarchiveInstance(inst.id);
// Rede, filtros de entrada e privacidade.
await bz.setInstanceProxy(inst.id, { proxy_url: "http://user:[email protected]:8080" });
await bz.setInboundFilters(inst.id, { ignore_groups: true, ignore_status: true });
await bz.setPrivacy(inst.id, { setting: "last", value: "contacts" });
await bz.deleteInstance(inst.id); // encerra a sessão e remove
await bz.getHealth(); // { status: "ok", version }
API 官方网站(WhatsApp Cloud API)
// Conexão: Embedded Signup (normal) ou credenciais manuais (phone_number_id opcional).
const { enabled, app_id, config_id } = await bz.getOfficialSignupConfig();
const { account, warning } = await bz.completeOfficialSignup({ code, waba_id: "…" });
await bz.connectOfficialAccount({ waba_id: "…", access_token: "…" });
const official = await bz.getOfficialAccount();
const { can_send, live } = await bz.getOfficialStatus(); // conferido ao vivo na Meta
// Vários números na mesma conta comercial.
const { numbers: available } = await bz.listAvailableOfficialNumbers();
const added = await bz.addOfficialNumber({ phone_number_id: available[0].id! });
const { numbers } = await bz.listOfficialNumbers(); // cada um com instance_id e is_default
await bz.setDefaultOfficialNumber(added.id!);
await bz.disconnectOfficialNumber(added.id!); // UM número; o histórico fica
// Perfil comercial (envie o perfil INTEIRO: campo vazio limpa na Meta).
const profile = await bz.getOfficialProfile();
await bz.updateOfficialProfile({ ...profile, about: "Atendimento 24/7" });
await bz.uploadOfficialProfilePhoto(await fileFromPath("./logo.png", "image/png"));
await bz.reregisterOfficialProfile(); // depois que a Meta aprova um nome novo
// Por número: getOfficialNumberProfile / updateOfficialNumberProfile /
// uploadOfficialNumberProfilePhoto / reregisterOfficialNumberProfile (id do número).
// Templates: validados ANTES de ir para a Meta; a decisão chega por webhook.
const { url } = await bz.uploadTemplateMedia(await fileFromPath("./amostra.png", "image/png"));
const { template, advice } = await bz.createTemplate({
name: "pedido_enviado",
language: "pt_BR",
category: "UTILITY",
header: { type: "image", media_url: url },
body: { text: "Olá {{1}}, seu pedido {{2}} saiu para entrega.", example: ["Ana", "#123"] },
});
const { templates } = await bz.listTemplates();
await bz.syncTemplates(); // importa os que existem só na Meta
await bz.sendTemplate({ to: "+5511999999999", template: "pedido_enviado", variables: ["Ana", "#123"] });
// Alternativa: com a janela de 24h fechada, o sendText sai como este template
// e a resposta conta o desvio em `delivery` ({ via: "template_fallback", template, note }).
await bz.setFallbackTemplate({ template_id: template!.id });
const sent = await bz.sendText({ to: "+5511999999999", body: "Oi!" });
if (sent.delivery) console.log(sent.delivery.note);
await bz.disconnectOfficialAccount(); // TODOS os números
游泳池
const pool = await bz.createPool({ name: "Vendas", strategy: "health_weighted" });
await bz.addPoolNumber(pool.id, { instance_id: inst.id });
await bz.getPool(pool.id);
const { data: pools } = await bz.listPools();
await bz.sendText({ to: "+5511999999999", body: "Oi!", pool_id: pool.id });
Perfil do número( 白色标签)
await bz.setProfile(inst.id, {
display_name: "Suporte bZapper",
status_message: "Atendimento 24/7",
picture: "<base64>", // opcional
});
status_message联合会;display_name电子picturesão experienceais (o servidor pode responseer) (英语:501) (中文(简体) ).
格鲁波斯语, 普森萨语 e conversas
作为歌剧 vançadas exigem um número específico。 说明o instance_id 发音:na 查询 (grups/conversas)你没有 机构 (presença/chats/contatos)——o SDK já cuida disso pela Assinatura de cada método. 中国植物物种信息数据库.
Presença (funciona em grupos!) (英语)
to 脚踏车,嗯 JID 德克鲁波 ——"数字和"("digitalando")的索引 funciona em grupos.
// Presença num grupo
await bz.presenceChat({
instance_id: inst.id,
to: "[email protected]", // JID de grupo
state: "typing", // typing | recording | paused
});
// Também em 1:1
await bz.presenceChat({ instance_id: inst.id, to: "+5511999999999", state: "recording" });
对话( 插件)
const { data: threads } = await bz.listConversations(inst.id);
const history = await bz.conversationHistory("[email protected]", {
instance_id: inst.id,
before: "2026-06-01T00:00:00Z", // opcional, RFC3339
limit: 100, // opcional, ≤ 200
});
await bz.archiveChat("[email protected]", { instance_id: inst.id, on: true });
await bz.pinChat("[email protected]", { instance_id: inst.id, on: true });
await bz.markChat("[email protected]", { instance_id: inst.id, on: true }); // lido
咕噜声
const { data: groups } = await bz.listGroups(inst.id);
const group = await bz.createGroup(inst.id, {
name: "Equipe bZapper",
participants: ["+5511988887777", "+5511977776666"],
});
await bz.getGroup(group.jid, inst.id);
await bz.updateGroupParticipants(group.jid, inst.id, {
action: "promote", // add | remove | promote | demote
participants: ["+5511988887777"],
});
const invite = await bz.groupInvite(group.jid, inst.id);
console.log(invite.invite_link);
await bz.previewGroupInvite(inst.id, { code: "AbCdEf123456" }); // nome/tamanho SEM entrar
await bz.joinGroup(inst.id, { code: "AbCdEf123456" });
await bz.leaveGroup(group.jid, inst.id);
await bz.updateGroup(group.jid, inst.id, { name: "Equipe", announce: true });
const fresh = await bz.groupInvite(group.jid, inst.id, { reset: true }); // revoga o link antigo
await bz.listJoinRequests(group.jid, inst.id);
await bz.updateJoinRequests(group.jid, inst.id, { participants: ["+5511988887777"], approve: true });
聊天、礼仪、礼仪
await bz.muteChat(jid, { instance_id: inst.id, on: true });
const label = await bz.createLabel({ instance_id: inst.id, name: "VIP" });
await bz.applyChatLabel(jid, { instance_id: inst.id, label_id: label.id!, apply: true });
await bz.listLabels(inst.id);
await bz.deleteLabel(label.id!, inst.id);
await bz.blockContact("[email protected]", { instance_id: inst.id });
await bz.getBlocklist(inst.id);
await bz.unblockContact("[email protected]", { instance_id: inst.id });
await bz.rejectCall({ instance_id: inst.id, call_from: "[email protected]", call_id: "…" });
孔塔多斯语Name
const { data } = await bz.contactsCheck({
instance_id: inst.id,
phones: ["+5511988887777", "+5511977776666"],
});
for (const c of data) console.log(c.query, c.in_whatsapp, c.jid);
Base de contatos(CRM),标签,grupos e supressão
O vínculo contato Projeto/número é mantido) 互联网档案馆的存檔,存档日期2013-12-02 自动降温 pela API; os filtros só leem. 中国植物物种信息数据库.
const page = await bz.listContacts({ tags: ["vip"], status: "active", has_email: true, limit: 50 });
const contact = await bz.createContact({ phone: "+5511988887777", name: "Ana", email: "[email protected]" });
await bz.updateContact(contact.id!, { address: { city: "São Paulo", state: "SP" } });
await bz.getContactHistory(contact.id!, { limit: 20 });
await bz.addContactNote(contact.id!, { body: "Pediu retorno à tarde" });
await bz.createTag({ key: "vip", name: "VIP" });
await bz.mutateContactTags(contact.id!, { add: ["vip"] });
await bz.createContactGroup({ key: "clientes", name: "Clientes" });
await bz.mutateContactGroups(contact.id!, { add: ["clientes"] });
await bz.optOutContact(contact.id!); // LGPD
await bz.optInContact(contact.id!);
await bz.createSuppression({ phone: "+5511977776666", reason: "pediu" });
await bz.deleteSuppression("+5511977776666");
await bz.deleteContact(contact.id!);
进口地块( E)importContacts(中文(简体) )
腾讯风,até 1000林纳 鸡尾酒 林哈·鲁伊姆·瓦伊(Linha ruim vai para) errors 电子 无
drruba o reso do lote; supresso/ opt-out/ 口述 平分 em skipped_rows (原始内容存档于2013-10-12). não resuscita.
(原始内容存档于2018-09-29). Tags e grupos são criados sob requesta. 新的香肠 pending_validation (中文(简体) )
(原始内容存档于2013-10-12). otto- in antes de campanha.
const rows = [
{ phone: "+5511988887777", name: "Ana", email: "[email protected]", tags: ["vip"], groups: ["clientes"] },
{ phone: "+5511977776666", name: "Bruno", address: { city: "São Paulo", state: "SP" } },
];
const dry = await bz.importContacts({ contacts: rows, dry_run: true }); // valida, não escreve
if (dry.failed === 0) {
const res = await bz.importContacts({ contacts: rows });
console.log(res.created, res.updated, res.skipped, res.failed);
for (const row of res.skipped_rows ?? []) console.warn(row.index, row.phone, row.reason);
}
出口 em CSV (exportContacts(中文(简体) )
阿西塔奥斯 膜膜 页:1 listContacts (声) offset· ; limit o teto de linhas) e e e a 维基语录链接:名人名言 - 文学作品 - 谚语
(原始内容存档于2018-03-21) (英语). unica Rota da SDK que jSON系统页:1 文本 CSV crus (单位:千美元)string(原始内容存档于2018-09-21) (中文(简体) )
(b) 国家、领土、领土、领土和领土.
import { writeFile } from "node:fs/promises";
const csv = await bz.exportContacts({ tags: ["vip"], status: "active", limit: 50_000 });
await writeFile("contatos.csv", csv, "utf8");
// phone,name,email,status,source,tags,groups,created_at,last_activity_at
// +5511988887777,"Silva, Ana",[email protected],active,import,vip;novo,clientes,2026-09-21T12:00:00Z,…
标记 e grupos vêm unidos por ; e 作为数据em RFC 3339(UTC). (原始内容存档于2018-09-21) (英语). Campos com vírgula ou Aspas vêm
escapados pelo paperão CSV——使用um parser de verdade se来再进口.
坎潘哈斯语Name
const camp = await bz.createCampaign({
name: "Black Friday",
pacing_profile: "conservative",
variations: [{ body: "Oi {nome}! {Oferta|Promoção} só hoje." }],
});
await bz.addCampaignRecipients(camp.id, { contact_filter: { tags: ["vip"] } });
await bz.getCampaignEligibility(); // números aptos (conexão e tempo de conexão)
const dry = await bz.dryRunCampaign(camp.id);
console.log(dry.estimated_human, dry.warnings);
await bz.startCampaign(camp.id);
await bz.pauseCampaign(camp.id);
await bz.resumeCampaign(camp.id);
// Imagem de cabeçalho (multipart).
import { fileFromPath } from "@bzapper/client";
const { url } = await bz.uploadCampaignMedia(await fileFromPath("./banner.png", "image/png"));
Projetos, marca, conta e usuários 互联网档案馆的存檔,存档日期2013-12-21
const proj = await bz.createProject({ name: "Loja 2" });
await bz.updateProject(proj.id, { name: "Loja SP", color: "#0a7" });
await bz.getProjectsHealth(); // semáforo de números por projeto
await bz.setProjectBrand(proj.id, { about: "Atendimento Loja SP" });
await bz.uploadProjectLogo(proj.id, { content: bytes, filename: "logo.png", contentType: "image/png" });
await bz.uploadBrandLogo(await fileFromPath("./logo.png", "image/png"));
const me = await bz.getMe();
await bz.updateProfile({ name: "Ana", job_title: "Suporte" });
await bz.updateAccount({ name: "Minha Empresa" });
await bz.inviteUser({ email: "[email protected]", role: "agent" });
Plano, 添加到e- faturas (英语)
const ent = await bz.getMyEntitlements();
await bz.upgradePlan(); // Pro no carrinho
await bz.changeAddon({ kind: "number", delta: 2 }); // +2 números
const cart = await bz.getAddonCart();
const { client_secret } = await bz.checkoutAddonCart({ save_card: true });
const { data: invoices } = await bz.listMyInvoices();
await bz.getPricing();
API 密钥( 自服务器)
const { data } = await bz.listKeys();
const created = await bz.createKey({ name: "CI", role: "agent" });
console.log(created.api_key); // guarde — mostrada só uma vez!
await bz.revokeKey(created.key.id);
* 部署rotateKey(中文(简体) )
rotateKey 克里亚·马沙夫 诺瓦 (赫尔达·帕佩尔、埃斯科波斯、诺梅) 反藻类
(原始内容存档于2018-03-25) (英语). funcionando por um período de carência - padrão 24 h, máximo 30 dias, 0 (原始内容存档于2013-10-12). Revoga na hora. 二级a
rova, roque o segredo onde a integração roda, e a antga morre sozinha (depois do prazo ela) 互联网档案馆的存檔,存档日期2013-12-02
响应 401 key_expired) (中文(简体) ). Só admin; a chave crua vem uma única vez. 中国植物物种信息数据库.
const rot = await bz.rotateKey(keyId, { revoke_in_seconds: 3600 }); // 1h de carência
console.log(rot.api_key); // guarde — mostrada só uma vez!
console.log(rot.old_key_expires_at); // quando a antiga para (null = revogada na hora)
console.log(rot.previous_key?.rotated_to === rot.key.id); // true
(bZapper Connection) 转接站 partner.rotateConnectionKey。 。 。 .
网络呼号
格伦西 网路上的相片:
const hook = await bz.createWebhook({
url: "https://seuapp.com/webhooks/bzapper",
event_types: ["message.received", "instance.banned"], // omita = todos os eventos
});
console.log(hook.secret); // segredo de assinatura — devolvido UMA vez, guarde agora
await bz.listWebhooks();
await bz.updateWebhook(hook.id, { active: false }); // pausar
await bz.updateWebhook(hook.id, { secret: "regenerate" }); // rotacionar segredo
await bz.deleteWebhook(hook.id);
Receba e 处理e 作为谷类 Webhooks assinatura HMAC, 维基百科中的相关条目: 活塞活塞,
e. even to tipado e roteia para seus 处理器(零)
依赖; usa o crypto nativo do 节点 :
import { Webhooks } from "@bzapper/client";
const hooks = new Webhooks(process.env.BZAPPER_WEBHOOK_SECRET!); // o secret do createWebhook
hooks.on("message.received", (event) => {
console.log(event.sender?.name, event.payload.body);
});
hooks.on("instance.banned", (event) => {
alert(event.instanceId);
});
// No seu endpoint HTTP. Passe o corpo CRU e o header X-Bzapper-Signature.
// Lança WebhookSignatureError se a assinatura for inválida — não processe nesse caso.
await hooks.handle(rawBody, signature);
欧 event tipado tem 电话 id, (中文(简体) ), type, (中文(简体) ), timestamp, (中文(简体) ), instanceId, (中文(简体) ),
clientReference, (中文(简体) ), group, (中文(简体) ), sender, (中文(简体) ), mentions, (中文(简体) ), payload e 个 raw 原文
(JSON做信封,EM蛇 case). 使用 event.id a API pode) 互联网档案馆的存檔,存档日期2013-12-02
(原始内容存档于2018-10-21). Reentregar. {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}我们的新浪微博 verifyWebhook(secret, rawBody, signature)
电子 constructWebhookEvent(secret, rawBody, signature)。 。 。 .
快递
Webhooks#middleware() 调出 um 中间软件 Estilo Express。 伊莱·普里西萨
铜屑,该寺 express.raw() 旋转正弦 req.body {\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}嗯 谢佳 嗯
Buffer (法语:não um objeto ja parseado):
import express from "express";
import { Webhooks } from "@bzapper/client";
const app = express();
const hooks = new Webhooks(process.env.BZAPPER_WEBHOOK_SECRET!);
hooks.on("message.received", (e) => console.log(e.payload.body));
app.post(
"/webhooks/bzapper",
express.raw({ type: "application/json" }), // entrega req.body como Buffer cru
hooks.middleware(), // verifica, dispara e responde 200/400
);
app.listen(3000);
无本地主机( T)bzapper listen(中文(简体) )
哦,帕科特特特车,嗯,执行。 URL pública Nenhuma, ele abre o 溪流
事件 e 本地服务中心时,屁股
igualzinho à produção(无埃斯皮里多) stripe listen:
npx @bzapper/client listen --forward-to http://localhost:3000/webhooks/bzapper
bZapper v0.16.0 — relay de webhooks para o localhost
ouvindo https://api.bzapper.com.br/webhooks/listen
reenviando http://localhost:3000/webhooks/bzapper
secret whsec_Hs3…
✓ conectado ao stream de eventos. Ctrl+C para sair.
14:02:11 message.received evt_01HZX… → 200 12ms
14:02:19 message.sent evt_01HZY… → 500 8ms
| 页:1 | |
|---|---|
-f, (中文(简体) ), --forward-to <url> | URL 本地 que recebe os POSTs (obrigatória, salvo com) (英语) --print-only(中文(简体) ) |
--api-key <key> | 键 bz_live_…页:1 $BZAPPER_API_KEY |
--base-url <url> | 基础 da API; 父版 $BZAPPER_BASE_URL {\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}你为我辩护 |
--project <id> | 塞西昂-塞西昂-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁-塞西翁 |
--events <a,b,c> | só estes tipos (前: message.received,message.sent(中文(简体) ) |
--secret <whsec_…> | segredo de assinatura; padrão: um novo, 有印象的动画 |
--print-only | 不,不,不,不,不,不,不 |
--help, (中文(简体) ), --version |
网络地图: o stream esppelha (英语: 待办事宜 连做
猪笼草(projeto, exista ou nuão uma assinatura)——由牛笼草(justamente o modo de senvolver antes)组成
de ter URL pública. 互联网档案馆的存檔,存档日期2014-12-02. 密钥无信头 Authorization尼尼卡纳URL.
卡达 POST 列瓦 Os mesmos headers da produção 互联网档案馆的存檔,存档日期2013-10-02 —— 说 X-Bzapper-Signature: sha256=<hmac do corpo cru>, (中文(简体) ), X-Bzapper-Event-Id, (中文(简体) ), X-Bzapper-Event-Type 电子
Content-Type: application/json ——,腾讯 Webhooks 中继代码
有效参数 a bZapper :
const hooks = new Webhooks(process.env.BZAPPER_WEBHOOK_SECRET!); // o secret que a CLI imprimiu
⚠️ 哦,秘密的,诺索. 萨姆
--secret一个CLI Gera um na hora e o (原始内容存档于2012-10-12) (英语). primate: use-o no sue app durante o teste. Assinatura Prova que o POST veio (英语) 脱去CLI, não da b Zapper—— la vale exatamente o que o secret vale. 中国植物物种信息数据库. 额 秘密 o do webhook cadastrado (英语:createWebhook) (中文(简体) ).
背对一个部分 retry 对
(一) 服务员; Ctrl+C {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}哦! {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}哦! Credencial 回避
(原始内容存档于2018-03-21). encerra na hora, com código de saida diferente de 0.
b Zapper 连接
段落 软件(原始内容存档于2018-03-26) (英语). o seu produto deixa os SEUS clients assinarem o
bZapper Pro e conectarem o WhatsApp sem sair dele, e recebe uma API 键
(单位:千美元)bz_live_...自动服务员 {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}哦 客户 {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}哦
bZapper serrata; a conexão só funciona enquanto o Pro dele estivers pago. 中国植物物种信息数据库.
O通量:
- 苏 后端 (cria uma sessão com o partners (cria uma sessão com com o partners secret)) 秘密合作(cria uma sessão com com o partners 秘密合作) 秘密合作(cria uma sessão com com o 伙伴合作(c
bz_partner_...) (中文(简体) ). - 苏 前面 abre 组件 com
session_token。 。 。 . - Ao concluir (Pro pago + número conectado), 构件 emite um 等
code(10分钟). - Seu 后端为 troca o
codepela API 密钥用于客户端 e guarda 密钥 . - Dali em diante , 请使用
Bzapper普通的相机 essa 密钥.
O 搭档秘密 e só de 后端 ——"无所事事"("dunica o mande par o navegador").
后端( Express)
import express from "express";
import {
Bzapper,
BzapperError,
BzapperPartner,
Webhooks,
isConnectEvent,
type PartnerWebhookEvent,
} from "@bzapper/client";
const app = express();
const partner = new BzapperPartner({ partnerSecret: process.env.BZAPPER_PARTNER_SECRET! });
// 1) Cria a sessão do componente para o cliente logado no SEU produto.
app.post("/bzapper/session", express.json(), async (req, res) => {
const user = req.user; // seu usuário autenticado
const session = await partner.createConnectSession({
external_id: user.id, // o id do cliente no SEU sistema (mesmo id = mesma conexão)
customer: {
name: user.name,
email: user.email,
phone: user.phone, // opcional, E.164 — pré-preenche o número
company: user.company, // opcional — vira o nome da conta/projeto
country: "BR", // opcional — define a moeda
},
locale: "pt-BR",
});
res.json({ session: session.session_token });
});
// 2) Troca o code emitido pelo componente pela API key do cliente.
app.post("/bzapper/exchange", express.json(), async (req, res) => {
const connection = await partner.exchangeCode(req.body.code);
// Guarde agora — a key não é mostrada de novo (use rotateConnectionKey se perder).
await db.saveBzapperKey(connection.external_id, connection.id, connection.api_key);
res.json({ status: connection.status });
});
// 3) Webhook do parceiro: mesma assinatura HMAC (X-Bzapper-Signature) dos webhooks
// normais, com o segredo do webhook do parceiro. Toda entrega traz `connection`.
const hooks = new Webhooks(process.env.BZAPPER_PARTNER_WEBHOOK_SECRET!);
hooks.onAny(async (event) => {
if (!isConnectEvent(event)) return; // mensagens/status dos números também chegam aqui
const { connection } = event as PartnerWebhookEvent;
switch (event.type) {
case "connect.completed":
case "connect.resumed":
await db.setBzapperStatus(connection.externalId, "active");
break;
case "connect.suspended": // Pro do cliente sem pagamento — volta sozinho ao pagar
await db.setBzapperStatus(connection.externalId, "suspended");
break;
case "connect.revoked": // encerrada — a key não vale mais
await db.deleteBzapperKey(connection.externalId);
break;
}
});
app.post(
"/webhooks/bzapper-partner",
express.raw({ type: "application/json" }), // corpo CRU, obrigatório para a assinatura
hooks.middleware(),
);
// 4) Usando a key do cliente: trate a suspensão (402) e a revogação (401).
app.post("/avisar", express.json(), async (req, res) => {
const bz = new Bzapper({ apiKey: await db.getBzapperKey(req.user.id) });
try {
await bz.sendText({ to: req.body.to, body: req.body.text });
res.sendStatus(202);
} catch (err) {
if (err instanceof BzapperError && err.code === "connect_suspended") {
// 402: o Pro do cliente está sem pagamento. Peça para ele regularizar.
return res.status(402).json({ error: "Regularize sua assinatura do bZapper." });
}
if (err instanceof BzapperError && err.code === "connect_revoked") {
// 401: a conexão acabou. Ofereça conectar de novo (nova sessão).
return res.status(409).json({ error: "Conecte o WhatsApp novamente." });
}
throw err;
}
});
app.listen(3000);
前线
<script src="https://widget.bzapper.com.br/v1/connect.js"></script>
<script>
async function conectarWhatsApp() {
const { session } = await fetch("/bzapper/session", { method: "POST" }).then((r) => r.json());
BzapperConnect.open({
session,
onComplete: ({ code }) =>
fetch("/bzapper/exchange", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code }),
}),
});
}
</script>
激素锥形
const me = await partner.me(); // de quem é o partner secret
const { data } = await partner.listConnections({ status: "suspended" }); // ou { external_id }
const conn = await partner.getConnection(data[0].id); // status, conta, números
const rotated = await partner.rotateConnectionKey(conn.id); // nova key; a anterior para de valer
console.log(rotated.api_key);
await partner.revokeConnection(conn.id); // revoga a key (NÃO cancela o plano do cliente)
埃斯塔多斯·达孔克桑(Estados da Conexão)status: pending_account, (中文(简体) ), pending_payment, (中文(简体) ),
pending_number, (中文(简体) ), active, (中文(简体) ), suspended 电子 revoked. eventos entlegues ao webhook (英语)
做小说: connect.completed, (中文(简体) ), connect.suspended, (中文(简体) ), connect.resumed 电子
connect.revoked (名单 CONNECT_EVENT_TYPES)),"活"(além dos eventos oldais),"活"(活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活"活
(原始内容存档于2018-09-30). dos números das conexõs antives. 欧 event.connection 调试 id, (中文(简体) ), externalId, (中文(简体) ),
accountId, (中文(简体) ), projectId 电子 status。 。 。 .
Lado do客户端 : apps conectados
API key da própria conta, o cliente vê e desconecta os parceiros: 互联网档案馆的存檔,存档日期2011-12-04
const { data: apps } = await bz.listConnectedApps(); // com partner_name / partner_logo_url
await bz.revokeConnectedApp(apps[0].id); // admin; a key do parceiro para na hora
Conector de IA (多功能软件公司)
Apps de IA (前: Claude) que uma pessoa autorizou a operator a conta pelo conector MCP: 水晶相机: 水晶相机:
const { data: conns } = await bz.listAIConnections(); // client_name, user_email, user_role, scopes, last_used_at
await bz.revokeAIConnection(conns[0].id); // os tokens dele param na próxima chamada
乌苏
const usage = await bz.getUsage({
from: "2026-06-01T00:00:00Z",
to: "2026-06-30T23:59:59Z",
});
console.log(usage.total, usage.delivery_rate);
诺瓦斯山地帐篷
Em qualquer resposta não-2xx o SDK lança (英语) BzapperError (ou uma sub classe.) (中文(简体) ). 拉米菲克铸币局
皮罗 code (stável),尼尼卡·皮洛(英语: message (文本为traduzido).
| 状态 | 类 | |
|---|---|---|
| 400,422 (中文(简体) ) | ValidationError | 铜矿/铜矿 |
| 401 (中文(简体) ) | AuthenticationError | 密钥 inválida/ revogada(重排)connect_revoked(中文(简体) ) |
| 403 (韩语) | PermissionDeniedError | requiredScope diz o escopo que faltou (法语) |
| 404 (韩语) | NotFoundError | |
| 409个 | ConflictError | 实例: not_connected, (中文(简体) ), idempotency_in_progress |
| 429个 | RateLimitError | retryAfter 自动交换 |
| 5 (简体中文) | ServerError | |
| sem reposta (中文(简体) ) | NetworkError | status = 0, (中文(简体) ), code = "NETWORK_ERROR" |
| (前402页) | BzapperError | 实例: connect_suspended |
托达斯牧民 BzapperError缩写: code, (中文(简体) ), message, (中文(简体) ), status (= (单位:千美元) statusCode, (中文(简体) ), locale, (中文(简体) ),
requestId (通知ao suporte),(请回答), retryAfter, (中文(简体) ), requiredScope 电子 body (公司解码器).
2xxx que não é JSON vira (英语) code = "INVALID_RESPONSE". . . . . , "." 尔 ".."
总统府 TypeError (原始内容存档于2018-03-26). antes de qualquer requisição.
import { Bzapper, 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); // ex.: "not_connected" 409 "a1b2…"
// Key de bZapper Connect: "connect_suspended" (402) e "connect_revoked" (401).
} else {
throw err;
}
}
诺瓦斯帐篷: (原始内容存档于2012-10-12) (英语). SDK tenta de novo sozinho em wrongo de rede/time out, 429, (中文(简体) ), 502, (中文(简体) ), 503 电子
504 (nada mais——嗯) 500 尔 4xx (原始内容存档于2018-09-21). volta na hora, até maxRetries 韦兹(padrão 2),埃斯佩兰多o
Retry-After (计到60分) min(8, 0,5 × 2^n) s + 25% de jitter.
同上: (POST/PUT/PATCH/DELETE) (波兰语) Idempotency-Key 盖拉多波尔
沙玛达e 重复 鼻涕虫 X-Request-Id)——一个API递出一个mesma
重新定义歌剧Idempotent-Replayed: true) (中文(简体) ). 龙卷风再现
接通地铁站:
await bz.sendText({ to: "+5511999999999", body: "Pedido #42 confirmado" }, { idempotencyKey: "pedido-42" });
await bz.createContact({ phone: "+5511999999999" }, { idempotencyKey: "crm-import-42" });
实例
韦雅 examples/quickstart.ts。 。 。 .
语法
麻省理工学院伯尼软件