Webhook
Aggiungi un endpoint nella dashboard e gli invieremo in POST ogni evento generato dalle tue chiavi, firmato con il segreto proprio dell’endpoint. I webhook sono inclusi in ogni piano.
Eventi
| Tipo | Quando | data contiene |
|---|---|---|
mark.completed | Un file marcato da POST /v1/mark (una clip quando il suo job è terminato) o un manifest firmato da POST /v1/sign (modalità locale). | mark_id, mode (cloud o local), project_id, kind (picture, song o clip), format, bytes, sha256, watermark_id, manifest, log_index, safety; per un brano e una clip, seconds e marks |
mark.failed | Una clip il cui job non è riuscito: bloccata dal controllo di sicurezza, oltre le marcature rimanenti, un file che non riusciamo a leggere o un errore da parte nostra. Non è stato marcato nulla. | mark_id, project_id, kind, error (code e message) e, per un fotogramma bloccato, safety |
check.blocked | Un’immagine o un fotogramma di una clip bloccato dal controllo di sicurezza, su POST /v1/check o con check su POST /v1/mark. | request_id, endpoint, project_id, policy, reason, picture (file o input) e, per una clip, il fotogramma. Mai l’immagine né il suo hash: non conserviamo nessuno dei due |
ping | Un test, inviato dalla dashboard a un endpoint. | message |
Un endpoint riceve gli eventi delle chiavi di produzione oppure di quelle di test, mai di entrambe, come avviene per le chiavi (ambienti). Il body:
POST application/json
{
"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"
}| Header | Valore |
|---|---|
Kennmarq-Signature | t=<secondi Unix>,v1=<HMAC-SHA256 esadecimale>: ciò che devi verificare prima di fidarti del body. |
Kennmarq-Event-Id | L’ID dell’evento (evt_…), lo stesso a ogni nuovo tentativo: usalo per gestire ogni evento una sola volta. |
Kennmarq-Event-Type | Il tipo dell’evento. |
Content-Type | application/json |
User-Agent | Kennmarq-Webhooks/1 |
Verifica ogni consegna
La firma è un HMAC-SHA256 dell’orario, di un punto e del body grezzo, con il segreto che la dashboard ti ha mostrato quando hai aggiunto l’endpoint (whsec_…). Segue lo schema di firma dei webhook di Stripe, quindi lo stesso codice di verifica funziona per entrambi. Confronta in tempo costante e rifiuta un orario che si discosti di più di cinque minuti, così una vecchia consegna non può essere riutilizzata.
Node.js
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);
}Python
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", ""))Verifica il body grezzo così come è arrivato, prima che lo tocchi qualsiasi parser JSON: un body riserializzato non corrisponderà.
Consegna e nuovi tentativi
- Una consegna è riuscita quando il tuo endpoint risponde 2xx entro 10 secondi. Prima rispondi, poi esegui il lavoro.
- Altrimenti riproviamo dopo 1 e 5 minuti, mezz’ora, poi 2, 6, 12 e 24 ore, e poi rinunciamo. Gli eventi possono arrivare più di una volta e fuori ordine: gestisci ogni
Kennmarq-Event-Iduna sola volta. - Gli endpoint sono HTTPS sulla porta standard, su internet pubblico: non seguiamo mai un redirect e non inviamo mai a un indirizzo privato, di loopback o link-local, qualunque sia l’indirizzo a cui si risolve un nome.
- La dashboard mostra le consegne recenti di ogni organizzazione; le conserviamo per 30 giorni.