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

Eventos de webhook
EventoSe envía cuando
list.updatedSe cargó un registro de una colección con cambios.
monitor.hitUna 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.changedUn registro de su lista de vigilancia fue modificado, retirado o fusionado.
webhook.testSe 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 Gone pausa 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, retrying o failed), 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.