API de mensajería

Un endpoint.Todos los canales.

POST /v1/messages acepta un número en E.164, una dirección de email, un chat ID numérico o un identificador de usuario de WhatsApp. Zavu elige el canal, registra la entrega y le cuenta a tu endpoint qué pasó.

to · E.164+14155551234
to · emailmaria@example.com
to · chat ID123456789
to · BSUIDUS.13491208655302741918
Canales
9 en un solo cuerpo
Envíos
Idempotentes por clave
Webhooks
Firmados con HMAC, v2
Lecturas
Paginadas por cursor

Constructor de request

Elige un canal. Lee el cuerpo.

La misma forma cubre SMS, WhatsApp, Telegram, email y voz. Cambia el canal y el tipo de mensaje: el request y la respuesta 202 son los que enviarías y recibirías de verdad.

zavu · request builderPOST /v1/messages
channel
messageType
opciones
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"
}
Respuesta · 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

El texto libre necesita una ventana de 24 horas abierta. Fuera de ella hay que enviar una plantilla: lo demás devuelve 400 whatsapp_window_closed.

Tres como máximo. El `id` pulsado vuelve en el webhook entrante.

Un tipo de mensaje distinto de texto se enruta a WhatsApp automáticamente, diga lo que diga `channel`.

SDKs

Envíalo desde tu stack.

TypeScript, Python y curl en cada operación que el SDK genera. Conversaciones y verificación de webhooks van como REST, porque el SDK todavía no las cubre.

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 oficiales: TypeScript, Python, Ruby, Go, PHP. La auth es un token Bearer; el override de sender es un header.

Entrega

Cada estado es un webhook.

Un mensaje pasa por queued, sent y delivered — y, en WhatsApp, read. Pulsa un paso para ver el evento exacto que recibe tu endpoint.

canal
desenlace
Payload del 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"
  }
}

Creado y encolado. Todavía no ha salido nada de Zavu.

Los estados también se leen cuando quieras: GET /v1/messages/{messageId}. El webhook es el push, no la única copia.

Firmas

Verifica antes de confiar.

Cada webhook lleva X-Zavu-Signature. El esquema que usa un sender es configurable, así que puedes mover un receptor sin perder un evento.

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

El esquema anterior a que esto fuera configurable. Los webhooks existentes siguen ahí hasta que los muevas.

v1+v2ambas, compartiendo un mismo t

El ajuste de migración. Un receptor que lea cualquiera de las dos funciona, así puedes desplegar y confirmar el verificador nuevo primero.

v2v2=HMAC_SHA256(secret, "{t}.{body}")

El esquema actual y el valor por defecto para senders nuevos. Firma el timestamp junto con el cuerpo.

Pasar de v1 directo a v2 devuelve 400. Primero pon v1+v2.

Rechazos

Lo que la API rechaza.

Cada uno de estos es una respuesta documentada, no un descarte silencioso. Lees el código y sabes qué hacer.

400whatsapp_window_closed

WhatsApp libre fuera de la ventana de 24 horas. Envía una plantilla o espera a que el contacto escriba.

403url_not_verified

Un SMS o email con una URL que nadie envió a verificar. Verifícala antes con POST /v1/urls.

403url_shortener_blocked

bit.ly, t.co y compañía esconden el destino. Usa la URL completa.

402insufficient_balance

SMS y voz se cobran por mensaje; el email por bloques. Con saldo cero el envío se rechaza en vez de encolarse.

429a2p_limit_exceeded

Se agotó la cuota compartida del plan Free. Se reinicia el día 1; los planes de pago no tienen tope de mensajes.

409idempotency conflict

Esa idempotencyKey ya envió un mensaje. Reintenta todo lo que quieras.

El email se revisa antes de salir

Un envío que sería un rebote duro garantizado se marca como fallido en vez de despacharse, así una lista mala no arrastra tu reputación. El mensaje pasa a `failed` con uno de estos códigos, visible en el mensaje y en message.failed.

EMAIL_INVALID_RECIPIENT
La dirección está mal formada.
EMAIL_DOMAIN_NOT_FOUND
El dominio no tiene registros MX ni A.
EMAIL_RECIPIENT_SUPPRESSED
Está en tu lista de supresión tras un rebote o una queja.

Cuotas

Lo que te cuesta un envío.

Dos medidores distintos. WhatsApp, Telegram, Instagram y Messenger comparten una cuota mensual; SMS, voz y email salen de tu saldo prepago.

WhatsApp · Telegram · Instagram · MessengerFree2.000 / mes, compartidos. Se reinicia el día 1.De pagoSin tope de mensajes.
SMS · VozFreePor mensaje, desde el saldo.De pagoPor mensaje, desde el saldo.
EmailFree$2 de crédito inicial · 3.000 / mes · 100 / díaDe pago$0,40 por 1.000 transaccionales · $0,80 por 1.000 de marketing

El email se cobra en bloques de 1.000 mensajes cada vez que tu conteo mensual cruza un límite. Los equipos en planes anteriores conservan sus cuotas originales.

/pricing

Endpoints

La superficie completa.

Enviar es una llamada. Todo lo que la rodea — leer un hilo, reaccionar, mostrar que estás escribiendo, bajar un adjunto — es una más.

POST/v1/messages

Enviar. Devuelve 202 con el mensaje encolado.

GET/v1/messages

Listar, filtrando por estado, destinatario o canal. Paginado por cursor.

GET/v1/messages/{messageId}

Un mensaje, con su costo y su conversationId.

POST/v1/messages/{messageId}/reactions

Reaccionar con un emoji. Solo WhatsApp.

POST/v1/messages/{messageId}/typing

Marcar leído y mostrar que escribes, hasta 25 segundos.

GET/v1/messages/{messageId}/attachments

URLs firmadas y efímeras para los adjuntos de email.

GET/v1/conversations

Hilos de la bandeja, los más activos primero.

GET/v1/conversations/{id}/messages

Un hilo, con todos los canales que ha llevado.

POST/v1/conversations/{id}/read

Poner el hilo en cero sin leer. Solo en tu bandeja.

Envía el primero.

Una key, un número, un POST. El resto de la superficie está ahí cuando la necesites.

API de mensajería | Un endpoint para SMS, WhatsApp, email y voz | Zavu