Webhooks
Ajoutez un endpoint dans le tableau de bord : nous lui envoyons en POST chaque événement que provoquent vos clés, signé avec le secret propre à cet endpoint. Toutes les offres en disposent.
Événements
| Type | Quand | Contenu de data |
|---|---|---|
mark.completed | Un fichier marqué par POST /v1/mark (une vidéo, une fois sa tâche terminée), ou un manifeste signé par POST /v1/sign (mode local). | mark_id, mode (cloud ou local), project_id, kind (picture, song ou clip), format, bytes, sha256, watermark_id, manifest, log_index, safety ; pour un morceau ou une vidéo, seconds et marks |
mark.failed | Une vidéo dont la tâche a échoué : bloquée par le contrôle de sécurité, au-delà des marquages restants, fichier illisible, ou défaillance de notre côté. Rien n’a été marqué. | mark_id, project_id, kind, error (code et message) et, pour une image bloquée, safety |
check.blocked | Une image, ou une image d’une vidéo, bloquée par le contrôle de sécurité, sur POST /v1/check ou avec check sur POST /v1/mark. | request_id, endpoint, project_id, policy, reason, picture (file ou input) et, pour une vidéo, frame. Jamais l’image ni son empreinte : nous n’en conservons aucune |
ping | Un test, envoyé depuis le tableau de bord à un endpoint. | message |
Un endpoint reçoit les événements des clés de production ou de celles de test, jamais des deux, comme les clés elles-mêmes (environnements). Le corps :
{
"created": 1793182831,
"data": {
"bytes": 1208033, "format": "image/png", "log_index": 4127, "manifest": "urn:c2pa:…",
"mark_id": "mk_4Q8oT1n6mWc2fLr0aZ9eYk", "mode": "cloud", "project_id": "prj_…",
"kind": "picture", "safety": null, "sha256": "…", "watermark_id": "1cfc5f99ffce3db9"
},
"id": "evt_9w1KQ0p3aZ8xLm2Tn4Rb6c",
"livemode": true,
"object": "event",
"type": "mark.completed"
}| En-tête | Valeur |
|---|---|
Kennmarq-Signature | t=<secondes Unix>,v1=<HMAC-SHA256 en hexadécimal> : ce qu’il faut vérifier avant de se fier au corps. |
Kennmarq-Event-Id | L’ID de l’événement (evt_…), identique à chaque nouvelle tentative : utilisez-le pour traiter chaque événement une seule fois. |
Kennmarq-Event-Type | Le type de l’événement. |
Content-Type | application/json |
User-Agent | Kennmarq-Webhooks/1 |
Vérifier chaque livraison
La signature est un HMAC-SHA256 de l’heure, d’un point et du corps brut, avec le secret que le tableau de bord a affiché quand vous avez ajouté l’endpoint (whsec_…). Elle suit le schéma de signature des webhooks de Stripe : le même code de vérification fonctionne donc pour les deux. Comparez en temps constant, et refusez une heure décalée de plus de cinq minutes, pour qu’une ancienne livraison ne puisse pas être rejouée.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(secret, rawBody, header, now = Date.now() / 1000) {
const parts = Object.fromEntries(header.split(",").map((p) => p.trim().split("=")));
const t = Number(parts.t);
if (!Number.isFinite(t) || Math.abs(now - t) > 300) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
const given = Buffer.from(parts.v1 ?? "", "hex");
return given.length === expected.length && timingSafeEqual(given, expected);
}import hashlib, hmac, time
def verify(secret: str, raw_body: bytes, header: str) -> bool:
parts = dict(p.strip().split("=", 1) for p in header.split(","))
t = int(parts.get("t", "0"))
if abs(time.time() - t) > 300:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))Livraison et nouvelles tentatives
- Une livraison est réussie quand votre endpoint répond 2xx en moins de 10 secondes. Répondez d’abord, puis faites le traitement.
- Sinon, nous réessayons après 1 et 5 minutes, une demi-heure, puis 2, 6, 12 et 24 heures, avant d’abandonner. Les événements peuvent arriver plusieurs fois et dans le désordre : traitez chaque
Kennmarq-Event-Idune seule fois. - Les endpoints sont en HTTPS sur le port standard, sur l’internet public : nous ne suivons jamais de redirection et n’envoyons jamais rien vers une adresse privée, de bouclage (loopback) ou link-local, quelle que soit la résolution d’un nom.
- Le tableau de bord affiche les livraisons récentes de chaque organisation ; nous les conservons 30 jours.