Webhooks
Cozter notifica a tu servidor cada vez que una guía cambia de estado. En lugar de consultar la API repetidamente, deja que Cozter te avise.
Registro
Un administrador registra la URL de tu endpoint (debe ser https://) desde la configuración del comercio. Al crearla, Cozter genera un secreto de firma (whsec_…) que se muestra una sola vez — guárdalo para verificar las firmas.
Carga útil
Cozter envía un POST con este cuerpo. El campo estado usa el vocabulario de estados. En entregado se incluye la prueba de entrega con enlaces firmados frescos.
{
"id": "3b1e…",
"evento": "guia.estado",
"estado": "entregado",
"guia": "WA-000100",
"external_ref": "PED-1023",
"fecha": "2026-07-21T18:30:05Z",
"datos": {
"destinatario": "Juan Pérez",
"telefono": "+50370000000",
"departamento": "San Salvador",
"municipio": "Soyapango",
"cod_amount": 25,
"pod": {
"entregado_en": "2026-07-21T18:30:00Z",
"recibido_por": "Juan Pérez",
"fotos": ["https://…firmado…"],
"firma": "https://…firmado…",
"pdf": "https://…firmado…/pod.pdf"
}
}
}
En estado fallido, datos incluye motivo en vez de pod. En estado incidencia, datos incluye motivo (accidente, cod_no_cobrado u otro) y detalle.
Encabezados
| Encabezado | Descripción |
|---|---|
X-Cozter-Event-Id | ID único del evento. Úsalo para deduplicar reintentos. |
X-Cozter-Timestamp | Unix en segundos, usado en la firma. |
X-Cozter-Signature | sha256=<hex> — HMAC-SHA256 del cuerpo. |
Verificar la firma
Calcula HMAC-SHA256(secreto, "{timestamp}.{cuerpo_crudo}") y compara con el encabezado X-Cozter-Signature. Usa el cuerpo tal cual llega, sin re-serializar el JSON.
import { createHmac, timingSafeEqual } from 'node:crypto'
export function verificarWebhook(req, secreto: string): boolean {
const ts = req.headers['x-cozter-timestamp']
const firma = req.headers['x-cozter-signature'] // "sha256=…"
const esperado
= 'sha256=' + createHmac('sha256', secreto)
.update(ts + '.' + req.rawBody) // cuerpo crudo
.digest('hex')
return timingSafeEqual(Buffer.from(firma), Buffer.from(esperado))
}
Reintentos
Responde 2xx para confirmar la entrega. Ante fallo o timeout (10 s), Cozter reintenta con backoff creciente:
1 min → 5 min → 30 min → 2 h → 6 h → 24 h
Tras 6 intentos fallidos, el evento se marca como fallido y no se reintenta más. Un endpoint caído nunca bloquea la entrega de los demás eventos.
X-Cozter-Event-Id para que un reintento no procese dos veces la misma actualización.