API de socios v1 · Borrador, aún no disponible
Webhooks
Borrador: aún no disponible. Esta página describe el contrato propuesto de la v1 para socios (versión 1.0.0-rc.1). Ningún punto de acceso de esta página está desplegado y todavía no se puede emitir ninguna clave de API.
Los webhooks le avisan a su sistema cuando cambió un registro del que usted depende, cuando cambió una entrada en vigilancia o cuando se modificó un registro de su lista de vigilancia. Cada entrega está firmada para que usted pueda comprobar que proviene de Crown Intelligence.
Eventos
| Evento | Se envía cuando |
|---|---|
list.updated | Se cargó un registro de una colección con cambios. |
monitor.hit | Una entidad, un registro o una colección en vigilancia fue incluido en una lista, excluido de la lista, modificado o fusionado, de una forma que abarca su notify_on. |
record.changed | Un registro de su lista de vigilancia fue modificado, retirado o fusionado. |
webhook.test | Se envía cuando usted llama a POST /v1/webhooks/{id}/test. No es posible suscribirse a este evento. |
Creación de un punto de acceso
El alcance requerido es webhooks:manage. La URL debe ser https://. Se rechazan las direcciones privadas, de bucle local (loopback) y de enlace local. El secreto de firma se devuelve una sola vez, en la respuesta de creación. Guárdelo en su gestor de secretos.
curl https://api.crownintelligence.net/v1/webhooks \
-H "X-Api-Key: $CI_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: hook-setup-0001" \
-d '{"url": "https://partner.example.com/crown/events",
"events": ["list.updated", "monitor.hit", "record.changed"],
"description": "Re-screening queue"}' La respuesta incluye el punto de acceso id, su estado y el secreto. Las lecturas posteriores solo muestran secret_prefix.
{
"id": "whk_01J9Z3K4M6AB",
"url": "https://partner.example.com/crown/events",
"events": ["list.updated", "monitor.hit", "record.changed"],
"status": "active",
"secret_prefix": "whsec_9f",
"secret": "whsec_9f2c...shown once..."
} Los puntos de acceso se modifican con PATCH (URL, eventos y active o paused). Deténgalos con POST /v1/webhooks/{id}/retire, lo que conserva su registro de entregas. Rote el secreto con POST /v1/webhooks/{id}/rotate-secret. El nuevo secreto se muestra una sola vez. Durante 24 horas se envían ambos secretos, de modo que puede cambiar de uno a otro sin perder ningún evento.
Verificación de la firma
Cada entrega incluye el encabezado Crown-Signature, t=<unix seconds>,v1=<hex>. El valor hexadecimal es el HMAC-SHA256, con clave en el secreto del punto de acceso, de la cadena <t>.<raw body>. Verifique en este orden: analice t y los valores de v1; rechace la entrega si t difiere en más de 300 segundos de su reloj; calcule el HMAC; compare en tiempo constante; y solo entonces analice el cuerpo.
Crown-Signature: t=1760364000,v1=<hex HMAC-SHA256>
Crown-Event-Id: evt_01J9Z4...
Crown-Delivery-Id: dlv_01J9Z5... (one per attempt; the event id is the same on retries)
Crown-Event-Type: list.updated Node
import crypto from "node:crypto";
// rawBody must be the exact bytes received, as a string. Do not re-serialise the JSON first.
export function verifyCrownSignature(rawBody, signatureHeader, secret) {
const parts = signatureHeader.split(",").map(p => p.trim().split("="));
const t = Number((parts.find(([k]) => k === "t") || [])[1]);
const v1 = parts.filter(([k]) => k === "v1").map(([, v]) => v); // two values during a secret rotation
if (!Number.isFinite(t) || v1.length === 0) return false;
// 1. Replay protection: refuse anything more than 300 seconds from now.
if (Math.abs(Math.floor(Date.now() / 1000) - t) > 300) return false;
// 2. HMAC-SHA256 over "<t>.<raw body>", keyed with the endpoint secret.
const expected = crypto.createHmac("sha256", secret).update(t + "." + rawBody).digest();
// 3. Constant-time comparison against each value.
return v1.some(hex => {
const given = Buffer.from(hex, "hex");
return given.length === expected.length && crypto.timingSafeEqual(given, expected);
});
} Use los bytes tal como llegaron. Muchos frameworks analizan el JSON antes de que se ejecute su código, lo que cambia los bytes e invalida la firma. Lea primero el cuerpo sin procesar.
Recepción de eventos
La entrega es de al menos una vez y sin orden garantizado. El mismo evento puede llegar más de una vez, con el mismo Crown-Event-Id en cada reintento. Use el ID del evento para evitar procesarlo dos veces y no dependa del orden de llegada.
// Verify first, then parse. Deduplicate on the event id: delivery is at least once and unordered.
const seen = new Set(); // use a durable store in production
export async function receive(req, rawBody) {
const ok = verifyCrownSignature(rawBody, req.headers["crown-signature"], process.env.CROWN_WEBHOOK_SECRET);
if (!ok) return 401;
const eventId = req.headers["crown-event-id"];
if (seen.has(eventId)) return 200; // a retry of an event already handled
const event = JSON.parse(rawBody); // only now parse the body
seen.add(eventId);
// ... queue event for re-screening using your own subject data ...
return 200; // any 2xx within 10 seconds counts as delivered
} Reintentos y registro de entregas
- Se considera exitosa cualquier respuesta 2xx dentro de 10 segundos.
- Cualquier otra respuesta, o un tiempo de espera agotado, se reintenta. Los reintentos se realizan a los 1, 5, 30, 120, 360, 720 y 1,440 minutos después del primer intento: ocho intentos en total, el último unas 45 horas después del primero.
- Una respuesta
410 Gonepausa el punto de acceso y detiene los reintentos. Crown Intelligence notifica al titular de la cuenta. - Cada intento aparece en
GET /v1/webhooks/{id}/deliveries, con su estado (pending,succeeded,retryingofailed), el código de respuesta, el último error y la hora del siguiente intento. El registro es de solo anexado.
Pruebe un punto de acceso con POST /v1/webhooks/{id}/test. Envía un evento webhook.test por la misma ruta de firma y entrega.
Los detalles de firma y reintentos siguen la sección 8 del borrador del contrato. Los valores de ejemplo son ilustrativos.