Přeskočit na obsah

Webhooks

Webhooks

Pomocí webhooků přijímáte upozornění v reálném čase, když v rámci vašeho workspace dojde k událostem — např. při naskenování QR kódu.

Vytvoření webhooku

Terminál
POST /v1/webhooks
{
"url": "https://example.com/webhooks/qr3",
"events": ["qr.scanned", "qr.created"],
"secret": "my-secret-key-min-16-chars"
}

Odpověď

{
"id": "wh_abc123",
"url": "https://example.com/webhooks/qr3",
"events": ["qr.scanned", "qr.created"],
"is_active": true,
"secret": "my-secret-key-min-16-chars",
"secret_hint": "my-s…",
"created_at": "2026-03-15T10:00:00.000Z"
}

Typy událostí

UdálostPopis
*Všechny události
qr.createdQR kód vytvořen
qr.updatedQR kód aktualizován (URL, stav, tagy)
qr.deletedQR kód smazán
qr.scannedQR kód naskenován
qr.flaggedQR kód označen jako nebezpečný
webhook.pingTestovací ping (přes endpoint /ping)

Formát payloadu

Všechny payloady webhooků mají tento formát:

{
"id": "evt_4f0c2a91d7e84b3c9a1f",
"type": "qr.scanned",
"created": "2026-03-15T10:00:00.000Z",
"data": {
"code_id": "qr_abc123",
"short_code": "r7f3Kx",
"scanned_at": "2026-03-15T10:00:00.000Z",
"country": "AT",
"device_type": "mobile",
"os": "iOS",
"browser": "Safari",
"language": "de",
"ab_variant": null,
"redirected_to": "https://example.com/landing"
}
}

Události kódů: qr.created a qr.updated

U těchto dvou událostí je data kód přesně tak, jak jej vrací GET /v1/codes/:id. To zahrnuje appearance, barvy uložené u kódu (null znamená výchozí černou, resp. bílou):

{
"id": "evt_9b2e41c07a5d4f18b3c6",
"type": "qr.updated",
"created": "2026-09-28T10:00:00.000Z",
"data": {
"id": "qr_abc123",
"short_code": "r7f3Kx",
"status": "live",
"appearance": { "foreground_color": "#1F4E79", "background_color": null },
"updated_at": "2026-09-28T10:00:00.000Z"
}
}

Příklad je zkrácen. Všechna pole naleznete v Codes API.

Ověření podpisu

qr3.app podepisuje každý požadavek webhooku pomocí HMAC-SHA256:

X-QR3-Signature: sha256=a1b2c3d4e5f6...
X-QR3-Event: qr.scanned
X-QR3-Delivery: wdl_abc123
X-QR3-Attempt: 1
User-Agent: qr3-webhooks/1.0

Implementace ověření

import crypto from "crypto";
function verifySignature(
payload: string,
signature: string | undefined,
secret: string
): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
const expectedBytes = Buffer.from(`sha256=${expected}`);
const receivedBytes = Buffer.from(signature ?? "");
return (
expectedBytes.length === receivedBytes.length &&
crypto.timingSafeEqual(expectedBytes, receivedBytes)
);
}
// In Express: verify the raw body — re-serialised JSON breaks the signature.
app.post(
"/webhooks/qr3",
express.raw({ type: "application/json" }),
(req, res) => {
const raw = req.body.toString("utf8");
const signature = req.headers["x-qr3-signature"] as string | undefined;
if (!verifySignature(raw, signature, process.env.QR3_WEBHOOK_SECRET!)) {
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(raw);
console.log(event.type, event.data);
res.json({ received: true });
},
);

Logika opakování

V případě chyb (HTTP >= 300 nebo timeout) se qr3.app pokusí o doručení znovu:

PokusZpoždění
1Okamžitě
21 minuta
35 minut

Po 10 po sobě jdoucích chybách bude webhook automaticky deaktivován.

Záruka doručení

qr3.app doručuje alespoň jednou, nikoli přesně jednou. Stejná událost proto může na váš endpoint dorazit více než jednou: jako opakovaný pokus po chybě nebo jako opakování pokusu, který už byl odeslán.

  • Duplicity odstraňujte podle id v payloadu (evt_…). Patří k události a zůstává stejné při každém pokusu i každém opakování. X-QR3-Delivery označuje jednotlivý pokus: opakování tohoto pokusu nese stejnou hodnotu, nový pokus novou. Již známé id potvrďte kódem 2xx, nikoli 4xx: 4xx se počítá jako neúspěšný pokus a po 10 po sobě jdoucích chybách se webhook automaticky deaktivuje.
  • Pokus, který je hodinu po vytvoření stále pending (například proto, že se jeho zpráva ve frontě ztratila), denní úklid jej nastaví na failed; status_code a response_time_ms zůstávají null. Nejde o chybu vašeho endpointu a nezapočítává se do 10 po sobě jdoucích chyb. Log pro něj ukládá text Delivery was not completed in time — closed by cleanup; koncový bod logů doručení jej nevrací. Uzavření není konečné: pokud zpráva přesto dorazí, je doručena a její skutečný výsledek tento text nahradí.

Logy doručení

Terminál
GET /v1/webhooks/:id/deliveries
{
"data": [
{
"id": "wdl_abc123",
"event_type": "qr.scanned",
"status": "success",
"status_code": 200,
"response_time_ms": 145,
"attempt": 1,
"created_at": "2026-03-15T10:00:00.000Z"
}
]
}

Pokusy o doručení se uchovávají 90 dní (aktuální den UTC a 89 předchozích dnů) a poté se automaticky mažou. Kromě zobrazených polí log ukládá payload události a první kilobajt (1 KB) odpovědi koncového bodu; tento koncový bod nevrací ani jedno z toho.

Testovací ping

Otestujte webhook okamžitě:

Terminál
POST /v1/webhooks/:id/ping

Tím se odešle událost webhook.ping na URL adresu vašeho endpointu.