API de mensagens
Um endpoint.Todos os canais.
POST /v1/messages aceita um número em E.164, um endereço de e-mail, um chat ID numérico ou um identificador de usuário do WhatsApp. A Zavu escolhe o canal, registra a entrega e conta ao seu endpoint o que aconteceu.
- Canais
- 9 em um só corpo
- Envios
- Idempotentes por chave
- Webhooks
- Assinados com HMAC, v2
- Leituras
- Paginadas por cursor
Construtor de request
Escolha um canal. Leia o corpo.
O mesmo formato cobre SMS, WhatsApp, Telegram, e-mail e voz. Troque o canal e o tipo de mensagem: o request e a resposta 202 são os que você enviaria e receberia de verdade.
- Authorization:
- Bearer zv_live_...
- Content-Type:
- application/json
- Zavu-Sender:
- sender_12345
{
"to": "+14155551234",
"channel": "whatsapp",
"messageType": "buttons",
"text": "How would you rate your experience?",
"content": {
"buttons": [
{
"id": "great",
"title": "Great"
},
{
"id": "okay",
"title": "It was okay"
},
{
"id": "poor",
"title": "Not good"
}
]
},
"idempotencyKey": "msg_01HZY4ZP7VQY2J3BRW7Z6G0QGE"
}{
"message": {
"id": "jd7x2k3m4n5p6q7r8s9t0",
"to": "+14155551234",
"from": "+13125551212",
"senderId": "sender_12345",
"channel": "whatsapp",
"messageType": "buttons",
"status": "queued",
"text": "How would you rate your experience?",
"conversationId": "js723987cyghwqxxaxcf590qd18axd95",
"createdAt": "2026-02-11T09:24:18.412Z"
}
}whatsapp · buttons
Texto livre precisa de uma janela de 24 horas aberta. Fora dela, envie um template — o resto devolve 400 whatsapp_window_closed.
Três no máximo. O `id` tocado volta no webhook de entrada.
SDKs
Envie a partir do seu stack.
TypeScript, Python e curl em cada operação que o SDK gera. Conversas e verificação de webhook aparecem como REST, porque o SDK ainda não as cobre.
import Zavudev from "@zavudev/sdk"
const zavu = new Zavudev({ apiKey: process.env.ZAVUDEV_API_KEY })
const { message } = await zavu.messages.send({
to: "+14155551234",
text: "Your order ORD-12345 has shipped.",
// channel defaults to "auto"; omit it and Zavu routes the message
idempotencyKey: "msg_01HZY4ZP7VQY2J3BRW7Z6G0QGE",
"Zavu-Sender": "sender_12345",
})
console.log(message.id, message.status) // -> "queued"SDKs oficiais: TypeScript, Python, Ruby, Go, PHP. A auth é um token Bearer; o override de sender é um header.
Entrega
Cada status é um webhook.
Uma mensagem passa por queued, sent e delivered — e, no WhatsApp, read. Clique em um passo para ver o evento exato que seu endpoint recebe.
{
"id": "evt_1786113454000_a1b2c3",
"type": "message.queued",
"timestamp": 1786113454000,
"senderId": "sender_12345",
"projectId": "prj_7a1e08d4",
"data": {
"messageId": "jx7744x3wckc9cwy0j9c8g6v0h7z7shs",
"to": "+14155551234",
"channel": "whatsapp",
"status": "queued"
}
}Criada e enfileirada. Nada saiu da Zavu ainda.
Assinaturas
Verifique antes de confiar.
Todo webhook leva X-Zavu-Signature. O esquema que um sender usa é uma configuração, então dá para mover um receptor sem perder um evento.
v1=HMAC_SHA256(secret, body)O esquema anterior a isto ser configurável. Os webhooks existentes ficam nele até você movê-los.
ambas, compartilhando um mesmo tA configuração de migração. Um receptor que leia qualquer uma funciona, então dá para publicar e confirmar o verificador novo antes.
v2=HMAC_SHA256(secret, "{t}.{body}")O esquema atual e o padrão para senders novos. Assina o timestamp junto com o corpo.
Ir de v1 direto para v2 devolve 400. Ponha v1+v2 primeiro.
Recusas
O que a API recusa.
Cada uma delas é uma resposta documentada, não um descarte silencioso. Você lê o código e sabe o que fazer.
WhatsApp livre fora da janela de 24 horas. Envie um template, ou espere o contato escrever.
Um SMS ou e-mail com uma URL que ninguém submeteu. Verifique antes com POST /v1/urls.
bit.ly, t.co e afins escondem o destino. Use a URL completa.
SMS e voz cobram por mensagem; e-mail cobra por blocos. Com saldo zero o envio é recusado em vez de enfileirado.
A cota compartilhada do plano Free acabou. Ela reinicia no dia 1; planos pagos não têm teto de mensagens.
Essa idempotencyKey já enviou uma mensagem. Pode tentar de novo à vontade.
O e-mail é conferido antes de sair
Um envio que seria um hard bounce garantido é marcado como falho em vez de despachado, para que uma lista ruim não derrube sua reputação. A mensagem vai para `failed` com um destes códigos, visível na mensagem e em message.failed.
- EMAIL_INVALID_RECIPIENT
- O endereço está malformado.
- EMAIL_DOMAIN_NOT_FOUND
- O domínio não tem registros MX nem A.
- EMAIL_RECIPIENT_SUPPRESSED
- Está na sua lista de supressão após um bounce ou uma reclamação.
Cotas
O que um envio custa.
Dois medidores diferentes. WhatsApp, Telegram, Instagram e Messenger dividem uma cota mensal; SMS, voz e e-mail saem do seu saldo pré-pago.
O e-mail é cobrado em blocos de 1.000 mensagens sempre que sua contagem mensal cruza um limite. Times em planos anteriores mantêm suas cotas originais.
/pricingEndpoints
A superfície inteira.
Enviar é uma chamada. Tudo em volta — ler um fio, reagir, mostrar que está digitando, baixar um anexo — é mais uma.
/v1/messagesEnviar. Devolve 202 com a mensagem enfileirada.
/v1/messagesListar, filtrando por status, destinatário ou canal. Paginado por cursor.
/v1/messages/{messageId}Uma mensagem, com seu custo e seu conversationId.
/v1/messages/{messageId}/reactionsReagir com um emoji. Só WhatsApp.
/v1/messages/{messageId}/typingMarcar como lida e mostrar que está digitando, até 25 segundos.
/v1/messages/{messageId}/attachmentsURLs assinadas e efêmeras para os anexos de e-mail.
/v1/conversationsFios da caixa de entrada, os mais ativos primeiro.
/v1/conversations/{id}/messagesUm fio, com todos os canais que já carregou.
/v1/conversations/{id}/readZerar os não lidos do fio. Só na sua caixa.
Envie a primeira.
Uma key, um número, um POST. O resto da superfície está lá quando você precisar.