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.

to · E.164+14155551234
to · emailmaria@example.com
to · chat ID123456789
to · BSUIDUS.13491208655302741918
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.

zavu · request builderPOST /v1/messages
channel
messageType
opções
Headers
Authorization:
Bearer zv_live_...
Content-Type:
application/json
Zavu-Sender:
sender_12345
Request
{
  "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"
}
Resposta · 202 Accepted
{
  "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.

Um tipo de mensagem que não seja texto vai para o WhatsApp automaticamente, diga o que disser `channel`.

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.

send.ts
npm add @zavudev/sdk
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.

canal
desfecho
Payload do webhook · message.queued
{
  "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.

Os status também podem ser lidos a qualquer momento: GET /v1/messages/{messageId}. O webhook é o push, não a única cópia.

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.

X-Zavu-Signature: t=1786113454,v2=b4b2b61cdcdb241e4270298a08c3c13c964642df…
v1v1=HMAC_SHA256(secret, body)

O esquema anterior a isto ser configurável. Os webhooks existentes ficam nele até você movê-los.

v1+v2ambas, compartilhando um mesmo t

A configuração de migração. Um receptor que leia qualquer uma funciona, então dá para publicar e confirmar o verificador novo antes.

v2v2=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.

400whatsapp_window_closed

WhatsApp livre fora da janela de 24 horas. Envie um template, ou espere o contato escrever.

403url_not_verified

Um SMS ou e-mail com uma URL que ninguém submeteu. Verifique antes com POST /v1/urls.

403url_shortener_blocked

bit.ly, t.co e afins escondem o destino. Use a URL completa.

402insufficient_balance

SMS e voz cobram por mensagem; e-mail cobra por blocos. Com saldo zero o envio é recusado em vez de enfileirado.

429a2p_limit_exceeded

A cota compartilhada do plano Free acabou. Ela reinicia no dia 1; planos pagos não têm teto de mensagens.

409idempotency conflict

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.

WhatsApp · Telegram · Instagram · MessengerFree2.000 / mês, compartilhados. Reinicia no dia 1.PagoSem teto de mensagens.
SMS · VozFreePor mensagem, do saldo.PagoPor mensagem, do saldo.
E-mailFreeUS$ 2 de crédito inicial · 3.000 / mês · 100 / diaPagoUS$ 0,40 por 1.000 transacionais · US$ 0,80 por 1.000 de marketing

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.

/pricing

Endpoints

A superfície inteira.

Enviar é uma chamada. Tudo em volta — ler um fio, reagir, mostrar que está digitando, baixar um anexo — é mais uma.

POST/v1/messages

Enviar. Devolve 202 com a mensagem enfileirada.

GET/v1/messages

Listar, filtrando por status, destinatário ou canal. Paginado por cursor.

GET/v1/messages/{messageId}

Uma mensagem, com seu custo e seu conversationId.

POST/v1/messages/{messageId}/reactions

Reagir com um emoji. Só WhatsApp.

POST/v1/messages/{messageId}/typing

Marcar como lida e mostrar que está digitando, até 25 segundos.

GET/v1/messages/{messageId}/attachments

URLs assinadas e efêmeras para os anexos de e-mail.

GET/v1/conversations

Fios da caixa de entrada, os mais ativos primeiro.

GET/v1/conversations/{id}/messages

Um fio, com todos os canais que já carregou.

POST/v1/conversations/{id}/read

Zerar 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.

API de mensagens | Um endpoint para SMS, WhatsApp, e-mail e voz | Zavu