API REST
A API do BeeZap segue REST simples sobre HTTPS. Toda chamada é autenticada via Authorization: Bearer com a chave do cliente.
Base URL
Todos os endpoints ficam em <URL_DO_HUB>/api/v1/.... Exemplo de produção: https://hub.exemplo.com.br/api/v1/messages.
Autenticação
Inclua o header Authorization: Bearer SUA_API_KEY. A chave começa com bz_ (chaves antigas começam com zh_ — ambas continuam válidas).
curl
curl <URL_DO_HUB>/api/v1/messages/123 \
-H "Authorization: Bearer bz_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"Respostas de erro de autenticação:
| Status | Motivo |
|---|---|
| 401 | Header ausente, vazio ou chave inválida |
| 403 | Cliente inativo |
POST /v1/messages
Dispara uma mensagem WhatsApp pelo hub.
Headers
| Header | Obrigatório | Descrição |
|---|---|---|
| authorization | sim | Bearer SUA_API_KEY |
| content-type | sim | application/json |
| idempotency-key | opcional | Identificador único do request. Se enviar a mesma key duas vezes, a 2ª chamada não duplica — devolve a mensagem original. |
Body
| Campo | Tipo | Descrição |
|---|---|---|
| chatId | string | JID do destinatário no WhatsApp. Ex: 5511999999999@c.us pra contatos, 1234567890@g.us pra grupos. |
| text | string? | Texto da mensagem (até 4096 chars). Obrigatório se não enviar mediaUrl. |
| mediaUrl | string? | URL pública de uma imagem/vídeo/áudio/documento. |
| mediaType | enum? | IMAGE | VIDEO | AUDIO | DOCUMENT. Obrigatório se mediaUrl for usado. |
| caption | string? | Legenda da mídia (até 1024 chars). |
| sessionId | string? | Força o envio por uma sessão específica (bypassa o router). Use só se souber o que tá fazendo — geralmente o router escolhe melhor. |
| sessionName | string? | Idem, mas por nome humano da sessão. |
| poolStrategy | enum? | STICKY | LEAST_USED | ROUND_ROBIN. Sobrescreve a estratégia padrão do cliente pra esta chamada. |
Resposta de sucesso
json
{
"messageId": "cmod1828ze00d2bl8sn3kr0w",
"externalMessageId": "true_5511999999999@c.us_3EB0...",
"wahaMessageId": "true_5511999999999@c.us_3EB0...",
"status": "SENT",
"session": {
"id": "ckxx...",
"name": "Zoom Principal",
"engine": "WAHA",
"routedReason": "sticky"
}
}wahaMessageId é um alias depreciado e idêntico a externalMessageId. Use o novo nome em código novo.
Use messageId pra consultar status depois (acks de entrega/leitura chegam pelo webhook ou via GET).
Erros possíveis
| Status | Erro | Quando |
|---|---|---|
| 400 | validação | Body inválido (campo faltando, tipo errado). |
| 404 | Session not found | Você passou sessionId/sessionName que não pertence ao cliente. |
| 409 | Idempotency key conflict | Mesma idempotency-key foi usada em outro cliente. |
| 409 | Session not connected | A sessão escolhida não tá CONNECTED. |
| 429 | Rate limit exceeded | Cota do cliente (msgs/min) estourada. Header retry-after indica espera. |
| 503 | No session available | Nenhuma sessão CONNECTED disponível (todas em cooldown ou daily cap atingido). Body detalha: no_sessions, all_in_cooldown ou all_at_cap. |
| 502 | Engine upstream | Falha conversando com a engine WhatsApp. O hub registra como FAILED e marca a sessão se passar do limite de erros consecutivos. |
Idempotência
Sempre que enviar mensagem disparada por evento (ex: lead chegou no CRM), gere uma idempotency-key determinística (ex: new-lead-{leadId}). Se o seu sistema retentar por timeout, o BeeZap não duplica.GET /v1/messages/:id
Consulta status atual de uma mensagem.
curl
curl <URL_DO_HUB>/api/v1/messages/cmod1828ze00d2bl8sn3kr0w \
-H "Authorization: Bearer bz_xxx..."json
{
"id": "cmod1828ze00d2bl8sn3kr0w",
"chatId": "5511999999999@c.us",
"direction": "OUTBOUND",
"status": "READ",
"externalMessageId": "true_5511...",
"wahaMessageId": "true_5511...",
"failReason": null,
"sentAt": "2026-04-27T13:42:01.123Z",
"deliveredAt": "2026-04-27T13:42:03.001Z",
"readAt": "2026-04-27T13:42:30.220Z",
"createdAt": "2026-04-27T13:42:00.998Z"
}Em geral, prefira receber acks via webhook em vez de polling.
Rate limits
- Por cliente: definido pelo admin do hub (default 60 msgs/min). Excede → 429 com
retry-after: 60. - Por sessão WhatsApp: cota diária (default 100/dia, ramp do warmup). Estouro retorna 503.
Exemplos
Node.js
js
async function sendWhatsApp(phone, text) {
const res = await fetch(`${process.env.HUB_URL}/api/v1/messages`, {
method: "POST",
headers: {
"content-type": "application/json",
authorization: `Bearer ${process.env.HUB_API_KEY}`,
"idempotency-key": `lead-${phone}-${Date.now()}`,
},
body: JSON.stringify({
chatId: `${phone.replace(/\D/g, "")}@c.us`,
text,
}),
});
if (!res.ok) throw new Error(`hub ${res.status}: ${await res.text()}`);
return res.json();
}PHP
php
<?php
function sendWhatsApp(string $phone, string $text): array {
$payload = [
'chatId' => preg_replace('/\D/', '', $phone) . '@c.us',
'text' => $text,
];
$ch = curl_init(getenv('HUB_URL') . '/api/v1/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'content-type: application/json',
'authorization: Bearer ' . getenv('HUB_API_KEY'),
'idempotency-key: lead-' . $phone . '-' . time(),
],
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($code >= 400) throw new RuntimeException("hub $code: $body");
return json_decode($body, true);
}Python
python
import os, requests, time
def send_whatsapp(phone: str, text: str) -> dict:
digits = "".join(c for c in phone if c.isdigit())
res = requests.post(
f"{os.environ['HUB_URL']}/api/v1/messages",
headers={
"authorization": f"Bearer {os.environ['HUB_API_KEY']}",
"content-type": "application/json",
"idempotency-key": f"lead-{phone}-{int(time.time())}",
},
json={"chatId": f"{digits}@c.us", "text": text},
timeout=15,
)
res.raise_for_status()
return res.json()