Webhooks
Recibe notificaciones firmadas en tu servidor cada vez que ocurre algo en el libro de reclamaciones.
En lugar de consultar la API cada cierto tiempo, puedes registrar un endpoint
HTTPS y Open Reclamos te enviará un POST con el evento en cuanto ocurra: un
consumidor registró un reclamo, tu equipo respondió, o cambió el estado.
Cada entrega va firmada con HMAC-SHA256 usando un secreto compartido, para que puedas verificar que realmente proviene de tu instancia.
Cómo funciona
Crea el endpoint en tu servidor
Una ruta pública POST que acepte JSON y responda 2xx rápidamente.
Regístralo en el dashboard
En Webhooks → Nuevo webhook indicas nombre, URL, eventos y el secreto de firma.
Verifica la firma
Compara el header X-Webhook-Signature con el HMAC que calculas localmente.
Procesa el evento
Encola el trabajo y responde de inmediato. Consulta la API si necesitas el detalle completo del reclamo.
Registrar un endpoint
Ve a Dashboard → Webhooks → Nuevo webhook y completa:
| Campo | Requisitos |
|---|---|
| Nombre | Entre 3 y 120 caracteres. Solo para identificarlo en la lista. |
| URL de destino | https:// público, hasta 2048 caracteres. |
| Eventos | Al menos uno de los tres eventos disponibles. |
| Secreto de firma | Entre 16 y 256 caracteres. Se guarda cifrado. |
| Estado | active recibe entregas; inactive las pausa sin borrar el endpoint. |
Solo destinos públicos
Por protección contra SSRF, se rechazan http://, localhost, IPs privadas
(10.x, 172.16-31.x, 192.168.x, 127.x, 169.254.x), rangos reservados y
direcciones IPv6 locales. La validación se repite resolviendo DNS en cada
entrega, así que un dominio público que apunte a una IP interna también se
bloquea. Para probar en local usa un túnel como ngrok o cloudflared.
Eventos disponibles
| Evento | Cuándo se dispara |
|---|---|
complaint.submitted | Un consumidor registró un nuevo reclamo o queja desde el libro público. |
complaint.responded | Se registró la respuesta oficial de la empresa. |
complaint.status_changed | Cambió el estado del reclamo (por ejemplo, de open a in_review). |
El esquema completo de cada evento está en la referencia de webhooks.
Formato de la entrega
Cada entrega es un POST con Content-Type: application/json y estos headers:
| Header | Descripción |
|---|---|
X-Webhook-Id | UUID único de esta entrega. Úsalo como clave de idempotencia. |
X-Webhook-Timestamp | Marca de tiempo Unix (segundos) usada al firmar. |
X-Webhook-Signature | sha256=<hmac_hex> del contenido firmado. |
El cuerpo siempre tiene la misma envoltura y un payload que depende del evento:
{
"event": "complaint.submitted",
"entityType": "complaint",
"entityId": "0192f3a0-9c5b-7b3e-8f1a-2c4d5e6f7a8b",
"organizationId": "0192f39a-3b4c-7d5e-8f6a-1b2c3d4e5f6a",
"payload": {
"trackingCode": "A1B2C3D4",
"correlative": "000123",
"type": "claim",
"status": "open"
},
"timestamp": "2026-01-15T14:32:10.123Z"
}Verificar la firma
La firma se calcula sobre la cadena {timestamp}.{cuerpo_crudo}:
firma = HMAC_SHA256(secreto, `${X-Webhook-Timestamp}.${rawBody}`)
header = "sha256=" + firma_en_hexadecimalUsa el cuerpo crudo
Firma exactamente los bytes que recibiste. Si parseas el JSON y lo vuelves a serializar, el orden de las claves o los espacios pueden cambiar y la firma no coincidirá.
import crypto from 'node:crypto'
const SECRET = process.env.OPEN_RECLAMOS_WEBHOOK_SECRET!
const TOLERANCE_SECONDS = 300
export function verifySignature(
rawBody: string,
timestampHeader: string | null,
signatureHeader: string | null,
): boolean {
if (!timestampHeader || !signatureHeader) return false
const timestamp = Number(timestampHeader)
if (!Number.isFinite(timestamp)) return false
// Rechaza entregas viejas o con reloj adelantado (ataques de repetición)
const now = Math.floor(Date.now() / 1000)
if (Math.abs(now - timestamp) > TOLERANCE_SECONDS) return false
const expected = `sha256=${crypto
.createHmac('sha256', SECRET)
.update(`${timestamp}.${rawBody}`)
.digest('hex')}`
const a = Buffer.from(expected)
const b = Buffer.from(signatureHeader)
if (a.length !== b.length) return false
// Comparación en tiempo constante
return crypto.timingSafeEqual(a, b)
}Reintentos y errores
Open Reclamos entrega cada evento a todos los endpoints activos suscritos, de forma independiente: si uno falla, no afecta a los demás.
| Respuesta de tu endpoint | Comportamiento |
|---|---|
2xx | Entrega marcada como sent. |
5xx, 408, 425, 429 | Se reintenta hasta 4 veces con backoff exponencial y jitter. |
Otros 4xx | Entrega marcada como failed, sin reintentos (indica un problema en tu handler). |
| Timeout | Reintento. El límite por defecto es de 15 segundos, configurable por endpoint. |
| URL insegura o secreto ausente | Entrega marcada como failed sin reintentos. |
Responde rápido
El tiempo de espera se cuenta contra tu endpoint completo. Valida la firma,
encola el evento y responde 2xx; deja el procesamiento pesado (enviar
correos, escribir en tu CRM) para un worker en segundo plano.
Depurar entregas
En Dashboard → Webhooks → Entregas encuentras el historial con el estado
(pending, sent, failed), el número de intentos, el código HTTP de respuesta
y los primeros 4000 caracteres del cuerpo devuelto por tu servidor.
- Verifica que el endpoint esté en estado
active. - Confirma que el evento esté marcado en la suscripción del endpoint.
- Revisa que la URL sea
https://y resuelva a una IP pública.
- Asegúrate de firmar el cuerpo crudo, no el JSON reserializado.
- El contenido firmado incluye el timestamp:
`${timestamp}.${rawBody}`. - Compara contra el header completo, incluyendo el prefijo
sha256=. - Revisa que el secreto sea el mismo que registraste en el dashboard.
Tu endpoint devolvió un 4xx distinto de 408, 425 o 429. Estos se
consideran errores permanentes: revisa el cuerpo de respuesta guardado en el
historial de entregas para ver qué devolvió tu servidor.
Buenas prácticas
- Suscríbete solo a los eventos que usas. Menos tráfico y menos superficie de error.
- Trata el payload como una notificación, no como la fuente de verdad. Si necesitas
todos los datos del reclamo, consúltalo con la API usando el
entityId. - Rota el secreto periódicamente desde el dashboard.
- Registra
X-Webhook-Idyeventen tus logs: hacen mucho más fácil correlacionar con el historial de entregas.