API partenaire v1 · Version préliminaire, pas encore en service
Webhooks
Version préliminaire, pas encore en service. Cette page décrit le contrat v1 proposé pour les partenaires (version 1.0.0-rc.1). Aucun point d’accès décrit ici n’est déployé, et aucune clé d’API ne peut encore être émise.
Les webhooks avisent votre système lorsqu’un registre dont vous dépendez a changé, lorsqu’une entrée surveillée a été modifiée ou lorsqu’une fiche de votre liste de surveillance a été modifiée. Chaque livraison est signée afin que vous puissiez vérifier qu’elle provient de Crown Intelligence.
Événements
| Événement | Envoyé lorsque |
|---|---|
list.updated | Un registre d’une collection a été chargé avec des modifications. |
monitor.hit | Une entité, une fiche ou une collection surveillée a été inscrite, radiée de la liste, modifiée ou fusionnée, d’une manière couverte par son notify_on. |
record.changed | Une fiche de votre liste de surveillance a été modifiée, retirée ou fusionnée. |
webhook.test | Envoyé lorsque vous appelez POST /v1/webhooks/{id}/test. Vous ne pouvez pas vous y abonner. |
Création d’un point d’accès
La portée requise est webhooks:manage. L’URL doit être https://. Les adresses privées, de bouclage (loopback) et de lien local sont refusées. Le secret de signature est renvoyé une seule fois, dans la réponse de création. Conservez-le dans votre gestionnaire de secrets.
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 réponse contient le point d’accès id, son statut et le secret. Les lectures ultérieures n’affichent que 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..."
} Les points d’accès sont modifiés avec PATCH (URL, événements, et active ou paused). Arrêtez-en un avec POST /v1/webhooks/{id}/retire, ce qui conserve son journal de livraison. Effectuez la rotation du secret avec POST /v1/webhooks/{id}/rotate-secret. Le nouveau secret n’est affiché qu’une seule fois. Pendant 24 heures, les deux secrets sont envoyés, ce qui vous permet de basculer sans perdre d’événement.
Vérification de la signature
Chaque livraison comporte l’en-tête Crown-Signature, t=<unix seconds>,v1=<hex>. La valeur hexadécimale est le HMAC-SHA256, calculé avec le secret du point d’accès, de la chaîne <t>.<raw body>. Effectuez la vérification dans cet ordre : analysez t et les valeurs v1; refusez la livraison si t s’écarte de plus de 300 secondes de votre horloge; calculez le HMAC; comparez en temps constant; n’analysez le corps de la requête qu’ensuite.
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);
});
} Utilisez les octets tels qu’ils sont arrivés. De nombreux cadriciels analysent le JSON avant l’exécution de votre code, ce qui modifie les octets et invalide la signature. Lisez d’abord le corps brut.
Réception des événements
La livraison est garantie au moins une fois, sans ordre précis. Un même événement peut arriver plus d’une fois, avec le même Crown-Event-Id à chaque nouvelle tentative. Utilisez l’identifiant de l’événement pour éviter de le traiter deux fois, et ne vous fiez pas à l’ordre d’arrivée.
// 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
} Nouvelles tentatives et journal de livraison
- Une livraison réussit si elle reçoit n’importe quelle réponse 2xx en moins de 10 secondes.
- Toute autre réponse, ou un délai dépassé, entraîne une nouvelle tentative. Les nouvelles tentatives ont lieu 1, 5, 30, 120, 360, 720 et 1 440 minutes après la première tentative : huit tentatives au total, la dernière environ 45 heures après la première.
- Une réponse
410 Gonemet le point d’accès en pause et arrête les nouvelles tentatives. Crown Intelligence avise le titulaire du compte. - Chaque tentative figure à
GET /v1/webhooks/{id}/deliveries, avec son statut (pending,succeeded,retryingoufailed), le code de réponse, la dernière erreur et l’heure de la prochaine tentative. Le journal est en mode ajout seulement.
Testez un point d’accès avec POST /v1/webhooks/{id}/test. Cela envoie un événement webhook.test par le même circuit de signature et de livraison.
Les détails sur la signature et les nouvelles tentatives suivent la section 8 du contrat préliminaire. Les valeurs d’exemple sont fournies à titre indicatif.