Open Reclamos
API

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:

CampoRequisitos
NombreEntre 3 y 120 caracteres. Solo para identificarlo en la lista.
URL de destinohttps:// público, hasta 2048 caracteres.
EventosAl menos uno de los tres eventos disponibles.
Secreto de firmaEntre 16 y 256 caracteres. Se guarda cifrado.
Estadoactive 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

EventoCuándo se dispara
complaint.submittedUn consumidor registró un nuevo reclamo o queja desde el libro público.
complaint.respondedSe registró la respuesta oficial de la empresa.
complaint.status_changedCambió 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:

HeaderDescripción
X-Webhook-IdUUID único de esta entrega. Úsalo como clave de idempotencia.
X-Webhook-TimestampMarca de tiempo Unix (segundos) usada al firmar.
X-Webhook-Signaturesha256=<hmac_hex> del contenido firmado.

El cuerpo siempre tiene la misma envoltura y un payload que depende del evento:

complaint.submitted
{
	"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_hexadecimal

Usa 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á.

webhook-handler.ts
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 endpointComportamiento
2xxEntrega marcada como sent.
5xx, 408, 425, 429Se reintenta hasta 4 veces con backoff exponencial y jitter.
Otros 4xxEntrega marcada como failed, sin reintentos (indica un problema en tu handler).
TimeoutReintento. El límite por defecto es de 15 segundos, configurable por endpoint.
URL insegura o secreto ausenteEntrega 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.

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-Id y event en tus logs: hacen mucho más fácil correlacionar con el historial de entregas.

On this page