Protokół webhook
Kształt payloadu, podpisywanie HMAC, polityka ponowień i wyłącznik bezpieczeństwa dla webhooków wychodzących.
Gdy zadanie osiąga stan terminalny (completed, failed, cancelled), dispatcher wysyła JSON body na callback_url instalacji.
Payload
{
"event": "job.completed",
"delivered_at": "2026-05-09T08:00:00.000Z",
"job": {
"id": "job_01h82k…",
"status": "completed",
"order_id": "ord_01h82k…",
"created_at": "2026-05-09T07:58:14.000Z"
},
"outputs": [
{
"url": "https://storage.qamera.ai/…?expires=…",
"type": "image/png",
"width": 1024,
"height": 1024
}
],
"external_metadata": { "your": "tag" }
}
outputs[].url jest presigned na 7 dni (zgodnie z POST /jobs/{id}/refresh-url). Odśwież przez ten endpoint, jeśli potrzebujesz świeżego URL po wygaśnięciu.
Podpisywanie
Każda delivery niesie:
X-Qamera-Signature: t=<unix-seconds>,v1=<hex_hmac_sha256>
Podpisywany string to <t>.<raw-body>. Odrzucaj podpisy, w których t jest starsze niż 5 minut.
Weryfikacja w Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody, headerValue, secret) {
const parts = Object.fromEntries(
headerValue.split(',').map((p) => p.split('=')).map(([k, v]) => [k, v]),
);
const t = parts.t;
const v1 = parts.v1;
if (!t || !v1) return false;
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex');
return timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(v1, 'hex'));
}
Weryfikacja w PHP
function qamera_verify($rawBody, $header, $secret) {
parse_str(strtr($header, ',', '&'), $parts);
if (!isset($parts['t']) || !isset($parts['v1'])) return false;
if (abs(time() - (int)$parts['t']) > 300) return false;
$expected = hash_hmac('sha256', $parts['t'] . '.' . $rawBody, $secret);
return hash_equals($expected, $parts['v1']);
}
Okno karencji rotacji
Po rotacji sekretu HMAC przez POST /installations/{id}/rotate-hmac poprzedni sekret pozostaje ważny przez 48h. W tym oknie deliveries niosą dwa segmenty v1= (po jednym na sekret); akceptuj którykolwiek. Przykładowy nagłówek:
X-Qamera-Signature: t=1746777600,v1=e3b0…,v1=44a0…
Po zakończeniu okna karencji tylko nowy sekret jest podpisywany.
Gwarancje niezawodności
- Do 8 prób, exponential backoff z jitter, cap 1 godzina.
- 5 kolejnych failów wstrzymuje deliveries na 30 minut (wyłącznik bezpieczeństwa).
- 3 cykle wstrzymania zawieszają instalację; admin musi ją odzawiesić.
- Po wszystkich próbach delivery jest oznaczona jako
abandonedi może być powtórzona przezPOST /webhooks/{delivery_id}/replay.
Twój endpoint powinien:
- Zwrócić dowolne
2xxdla zaakceptowanych eventów. - Procesować eventy idempotentnie — dispatcher może ponowić po wolnym
200. - Persystować (event, job_id, delivered_at) dla audytu; dispatcher sam z siebie nie wysyła ponownie potwierdzonych eventów.