Webhooks
Los webhooks son llamadas POST que la API hace a una dirección de su sistema cuando algo ocurre: llegó un mensaje, se envió un mensaje, el número se conectó o se desconectó.
Eventos
Sección titulada «Eventos»| Evento | Cuándo ocurre |
|---|---|
message.received |
Llegó un mensaje |
message.sent |
Se envió un mensaje de la cola |
message.status |
Cambió la situación de un mensaje (incluye fallos de envío) |
presence |
Cambio de presencia de un contacto, como “escribiendo”. El cuerpo trae chatId y presence |
instance.connected |
El número se conectó. El cuerpo trae phone |
instance.disconnected |
El número se desconectó. El cuerpo trae reason y loggedOut |
contact.opted_out |
Un contacto pidió no recibir más mensajes. El cuerpo trae phone |
Configurar
Sección titulada «Configurar»PUT /v1/instances/{instanceId}/webhooks reemplaza toda la configuración. Envíe siempre el conjunto completo. Cada evento tiene su propia URL y puede activarse o desactivarse:
curl -X PUT https://api.zap.nigora.com.br/v1/instances/INSTANCIA_ID/webhooks \ -H "Authorization: Bearer TOKEN" \ -H "Content-Type: application/json" \ -d '{ "events": { "message.received": { "url": "https://meusistema.com.br/hooks/recebida", "enabled": true }, "message.status": { "url": "https://meusistema.com.br/hooks/status", "enabled": true } }, "notifySentByMe": false, "receiveFilters": { "ignoreGroups": true } }'La respuesta (200) trae la configuración guardada y el secret, el secreto que se usa en la firma.
Las URL pueden tener hasta 2.048 caracteres y deben ser públicas. Las direcciones de la red interna y de loopback (como localhost) se rechazan con 400 y url_not_allowed. Los campos que no sigan el formato anterior también devuelven 400 (validation_error). Los eventos sin URL, o con enabled: false, no generan llamadas.
Filtros de recepción
Sección titulada «Filtros de recepción»En receiveFilters, cada campo es false de forma predeterminada. Actívelo para no recibir ese tipo de mensaje:
ignoreGroups, ignorePrivate, ignoreText, ignoreImage, ignoreVideo, ignoreAudio, ignoreDocument.
Con notifySentByMe activado, usted también recibe message.received para los mensajes que usted mismo envió desde el celular.
Para leer la configuración actual, use GET /webhooks. La respuesta incluye el secreto de la firma.
Lo que envía la API
Sección titulada «Lo que envía la API»El cuerpo es siempre JSON, con este formato:
{ "event": "message.received", "instanceId": "…", "timestamp": "2026-11-02T14:30:00.000Z", "data": { }}data cambia según el evento. En message.received:
| Campo | Descripción |
|---|---|
waMessageId |
Identificador del mensaje en WhatsApp |
chatId |
Conversación, como 5511999999999@s.whatsapp.net (los grupos terminan en @g.us) |
from |
Quién lo envió, en el mismo formato |
fromMe |
true si lo envió el propio número |
isGroup |
true si vino de un grupo |
timestamp |
Hora del mensaje, en segundos desde 1970 |
type |
text, image, video, audio, document u other |
text |
Texto, o null |
quotedMessageId |
Mensaje respondido, o null |
pushName |
Nombre visible de quien lo envió, o null |
media |
Para multimedia: url, mimeType, fileName y expiresAt |
mediaSkipped |
true si el archivo no pudo descargarse o era demasiado grande |
En message.sent, data trae id (el devuelto en el 202 del envío), waMessageId y type. En message.status, trae id, waMessageId y status (sent, delivered, read o failed); cuando el envío falla, viene también error (vea Envío de mensajes).
Encabezados
Sección titulada «Encabezados»| Encabezado | Contenido |
|---|---|
x-nigora-event |
Nombre del evento |
x-nigora-delivery |
Identificador de esta entrega |
x-nigora-signature |
Firma del cuerpo, con el formato sha256=<hex> |
Verifique la firma
Sección titulada «Verifique la firma»Antes de confiar en una llamada, compruebe la firma. Es el HMAC-SHA256 de los bytes exactos del cuerpo recibido, calculado con el secreto de la instancia (el secret de GET /webhooks) y escrito en hexadecimal, después del prefijo sha256=.
import { createHmac, timingSafeEqual } from 'node:crypto'
// `corpoBruto` es el cuerpo recibido, sin pasar por JSON.parse ni volver a serializarse.function assinaturaValida(corpoBruto, cabecalho, segredo) { const esperada = 'sha256=' + createHmac('sha256', segredo).update(corpoBruto).digest('hex') const a = Buffer.from(esperada) const b = Buffer.from(cabecalho ?? '') return a.length === b.length && timingSafeEqual(a, b)}<?php$corpo = file_get_contents('php://input');$esperada = 'sha256=' . hash_hmac('sha256', $corpo, $segredo);if (!hash_equals($esperada, $_SERVER['HTTP_X_NIGORA_SIGNATURE'] ?? '')) { http_response_code(401); exit;}Para cambiar el secreto, llame a POST /webhooks/secret/regenerate. La respuesta es { "secret": "..." } y el anterior deja de valer. Los intentos y reenvíos hechos después del cambio ya salen firmados con el secreto nuevo.
Responda rápido y con 2xx
Sección titulada «Responda rápido y con 2xx»Responda con cualquier estado 2xx para confirmar, en un máximo de 10 segundos. Cualquier otra respuesta, o la falta de respuesta, cuenta como fallo y la API vuelve a intentarlo, con una espera creciente entre intentos (la primera espera es de 5 segundos y se duplica con cada fallo), hasta 8 intentos en total. Después de eso, la entrega queda como failed.
Haga el procesamiento pesado después de responder. Confirme primero y trabaje a continuación, de preferencia en una cola propia.
Como puede haber reenvíos, use el encabezado x-nigora-delivery para ignorar una entrega que usted ya procesó.
Historial y reenvío
Sección titulada «Historial y reenvío»La API guarda las entregas de los últimos 7 días.
| Operación | Ruta |
|---|---|
| Listar entregas, de la más reciente a la más antigua | GET /webhooks/deliveries (parámetros limit, de 1 a 100, y before, una fecha ISO 8601 con zona horaria, para paginar) |
| Ver una entrega: evento, situación, intentos y contenido | GET /webhooks/deliveries/{id} |
| Reenviar una entrega | POST /webhooks/deliveries/{id}/resend |
La lista viene en { "deliveries": [...] }. Cada entrega trae id, event, url, status (pending, success o failed), attempts, lastStatusCode, lastResponse, createdAt y updatedAt. La consulta de una entrega agrega el payload.
El reenvío responde 202 con { "id": "..." }, el identificador de una nueva entrega con el mismo contenido, a la misma URL. Un id que no existe en esta instancia devuelve 404 con webhook_delivery_not_found.
El historial es útil cuando su sistema estuvo fuera de servicio: liste las entregas con fallo y reenvíelas.
Baja de contactos
Sección titulada «Baja de contactos»Active la baja en PATCH /settings:
curl -X PATCH https://api.zap.nigora.com.br/v1/instances/INSTANCIA_ID/settings \ -H "Authorization: Bearer TOKEN" \ -H "Content-Type: application/json" \ -d '{ "optOut": { "enabled": true, "keywords": ["SAIR", "PARAR"] } }'Cuando un contacto envía, en una conversación privada, un mensaje que sea solo una de esas palabras (sin distinguir mayúsculas, minúsculas ni acentos), entra en la lista de bajas y usted recibe el evento contact.opted_out. Los nuevos envíos a ese contacto se rechazan con 422 (contact_opted_out). La palabra predeterminada es SAIR.
GET /opt-outslista los contactos dados de baja (conlimitybeforepara paginar).DELETE /opt-outs/{phone}permite volver a enviar a un teléfono. Responde204, aunque el teléfono no estuviera en la lista.