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ó.
- 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.
- 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
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.
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.
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.
{
"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.
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.
v1=HMAC_SHA256(secret, body)El esquema anterior a que esto fuera configurable. Los webhooks existentes siguen ahí hasta que los muevas.
ambas, compartiendo un mismo tEl ajuste de migración. Un receptor que lea cualquiera de las dos funciona, así puedes desplegar y confirmar el verificador nuevo primero.
v2=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.
WhatsApp libre fuera de la ventana de 24 horas. Envía una plantilla o espera a que el contacto escriba.
Un SMS o email con una URL que nadie envió a verificar. Verifícala antes con POST /v1/urls.
bit.ly, t.co y compañía esconden el destino. Usa la URL completa.
SMS y voz se cobran por mensaje; el email por bloques. Con saldo cero el envío se rechaza en vez de encolarse.
Se agotó la cuota compartida del plan Free. Se reinicia el día 1; los planes de pago no tienen tope de mensajes.
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.
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.
/pricingEndpoints
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.
/v1/messagesEnviar. Devuelve 202 con el mensaje encolado.
/v1/messagesListar, filtrando por estado, destinatario o canal. Paginado por cursor.
/v1/messages/{messageId}Un mensaje, con su costo y su conversationId.
/v1/messages/{messageId}/reactionsReaccionar con un emoji. Solo WhatsApp.
/v1/messages/{messageId}/typingMarcar leído y mostrar que escribes, hasta 25 segundos.
/v1/messages/{messageId}/attachmentsURLs firmadas y efímeras para los adjuntos de email.
/v1/conversationsHilos de la bandeja, los más activos primero.
/v1/conversations/{id}/messagesUn hilo, con todos los canales que ha llevado.
/v1/conversations/{id}/readPoner 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.