Saltar al contenido principal

Configurar

Registrar un endpoint​

curl -X POST https://api.joinotify.com/webhook-endpoints \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-d '{
"name": "produccion",
"url": "https://tu-servidor.com/webhooks/joinotify",
"events": ["messages"]
}'
201 Created
{
"data": {
"id": "whe_01J8ZK...",
"name": "produccion",
"url": "https://tu-servidor.com/webhooks/joinotify",
"events": ["messages"],
"active": true,
"secret": "whsec_AbCdEf0123456789abcdefgh"
},
"warning": "Store the \"secret\" now — use it to validate the X-Joinotify-Signature-256 header."
}

A diferencia de las claves de API, el secreto de webhook sí puede consultarse después:

curl https://api.joinotify.com/webhook-endpoints/whe_01J8ZK.../secret \
-H 'Authorization: Bearer sk_live_xxx'

Requisitos de la URL​

En producción rechazamos:

  • http:// — solo HTTPS
  • Hosts privados: localhost, 127.*, 10.*, 192.168.*, 169.254.*, 172.16-31.*, *.internal, *.local

Es una protección contra SSRF: sin ella, un endpoint apuntando a la red interna se convertiría en un escáner.

Para desarrollo local, usa un túnel:

ngrok http 3000

Filtros​

curl -X POST https://api.joinotify.com/webhook-endpoints \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-d '{
"name": "atencion-tienda-1",
"url": "https://tu-servidor.com/webhooks/tienda-1",
"events": ["messages", "smb_message_echoes"],
"phoneNumberId": "106540352242922"
}'
CampoEfecto
events: []Suscribe a todos los eventos
events: ["messages"]Solo lo que esté en la lista
wabaIdSolo eventos de esa cuenta de empresa
phoneNumberIdSolo eventos de ese número
El filtro por número descarta los eventos sin número

Los eventos de cuenta (account_update, account_alerts) y de plantilla (message_template_status_update) no tienen número asociado. Un endpoint filtrado por phoneNumberId no los recibe.

Si quieres todo, deja un endpoint sin filtro.

Varios endpoints​

Nada impide tener más de uno — y suele ser buena idea:

# Uno para el producto
{ "name": "app", "url": "https://app.ejemplo.com/webhooks", "events": ["messages"] }

# Otro para auditoría, suscrito a todo
{ "name": "auditoria", "url": "https://logs.ejemplo.com/webhooks", "events": [] }

Cada endpoint tiene su propio secreto, su propia cola y su propio contador de fallos. Un endpoint roto no afecta a los demás.

Probar​

curl -X POST https://api.joinotify.com/webhook-endpoints/whe_01J8ZK.../test \
-H 'Authorization: Bearer sk_live_xxx'

Dispara un evento sintético y lo entrega al instante, sin pasar por la cola. La respuesta trae el estado HTTP y el cuerpo que devolvió tu servidor — la forma más rápida de comprobar si tu validación de firma es correcta.

{
"data": {
"id": "whd_01J8ZM...",
"status": "delivered",
"responseStatus": 200,
"responseBody": "OK",
"durationMs": 142
}
}

El payload de prueba es un evento messages con joinotify_test: true — filtra por ese campo si tu handler guarda todo en la base de datos.

Actualizar​

curl -X PATCH https://api.joinotify.com/webhook-endpoints/whe_01J8ZK... \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-d '{ "events": ["messages", "smb_message_echoes", "history"] }'

Los campos omitidos se quedan como están. Reactivar un endpoint desactivado (active: true) pone a cero el contador de fallos.

Pausar y eliminar​

# Pausar — conserva historial y secreto
curl -X PATCH https://api.joinotify.com/webhook-endpoints/whe_01J8ZK... \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-d '{ "active": false }'

# Eliminar — descarta las entregas pendientes
curl -X DELETE https://api.joinotify.com/webhook-endpoints/whe_01J8ZK... \
-H 'Authorization: Bearer sk_live_xxx'

Para un mantenimiento, pausa. Eliminar es definitivo.

Rotar el secreto​

curl -X POST https://api.joinotify.com/webhook-endpoints/whe_01J8ZK.../rotate-secret \
-H 'Authorization: Bearer sk_live_xxx'

El cambio es inmediato y no hay período de convivencia entre los dos secretos. Dos formas de hacerlo sin perder eventos:

Sin caída — haz que tu handler acepte dos secretos durante un tiempo: valida contra el nuevo y, si falla, contra el antiguo. Rota, confirma, retira el antiguo.

Aceptando fallos — rota y publica el nuevo secreto. Las entregas que fallen en el intervalo se reintentan automáticamente en 60 segundos.