Skip to content

Webhooks

Webhooks

Pomoću webhooka primate obavijesti u stvarnom vremenu kada se dogode događaji u vašem radnom prostoru — npr. kada se skenira QR kod.

Izrada webhooka

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

Odgovor

{
"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"
}

Vrste događaja

DogađajOpis
*Svi događaji
qr.createdQR kod je kreiran
qr.updatedQR kod je ažuriran (URL, status, oznake)
qr.deletedQR kod je obrisan
qr.scannedQR kod je skeniran
qr.flaggedQR kod je označen kao nesiguran
webhook.pingTestni ping (putem /ping krajnje točke)

Format payload-a

Svi webhook payload-i imaju ovaj format:

{
"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"
}
}

Događaji koda: qr.created i qr.updated

Kod ova dva događaja, data je kod točno onakav kakav ga vraća GET /v1/codes/:id. To uključuje appearance, boje pohranjene na kodu (null označava standardnu crnu, odnosno bijelu):

{
"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"
}
}

Primjer je skraćen. Sva polja nalaze se u Codes API-ju.

Verifikacija potpisa

qr3.app potpisuje svaki webhook zahtjev pomoću 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

Implementacija verifikacije

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 ponovnog pokušaja

U slučaju pogrešaka (HTTP >= 300 ili istek vremena), qr3.app ponovno pokušava dostavu:

PokušajOdgoda
1Odmah
21 minuta
35 minuta

Nakon 10 uzastopnih pogrešaka, webhook se automatski deaktivira.

Jamstvo dostave

qr3.app dostavlja barem jednom, a ne točno jednom. Isti se događaj stoga može više puta pojaviti na vašoj krajnjoj točki: kao ponovni pokušaj nakon pogreške ili kao ponavljanje pokušaja koji je već poslan.

  • Duplikate uklanjajte pomoću id iz payloada (evt_…). Pripada događaju i ostaje isti pri svakom pokušaju i svakom ponavljanju. X-QR3-Delivery označava pojedinačni pokušaj: ponavljanje tog pokušaja nosi istu vrijednost, novi pokušaj novu. Već poznati id potvrdite kodom 2xx, a ne 4xx: 4xx se računa kao neuspjeli pokušaj, a nakon 10 uzastopnih pogrešaka webhook se automatski deaktivira.
  • Pokušaj koji je sat vremena nakon stvaranja i dalje pending (na primjer jer se njegova poruka u redu izgubila) dnevno čišćenje ga postavlja na failed; status_code i response_time_ms ostaju null. To nije pogreška vaše krajnje točke i ne ubraja se u 10 uzastopnih pogrešaka. Zapisnik za to pohranjuje tekst Delivery was not completed in time — closed by cleanup; krajnja točka za zapise o dostavi ne vraća ga. Zatvaranje nije konačno: ako poruka ipak stigne, dostavlja se, a njezin stvarni rezultat zamjenjuje taj tekst.

Zapisi o dostavi

Terminal window
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"
}
]
}

Pokušaji dostave čuvaju se 90 dana (tekući UTC dan i 89 prethodnih dana), a zatim se automatski brišu. Osim prikazanih polja, zapisnik pohranjuje payload događaja i prvi kilobajt (1 KB) odgovora krajnje točke; ta krajnja točka ne vraća ništa od toga.

Testni ping

Odmah testirajte webhook:

Terminal window
POST /v1/webhooks/:id/ping

To šalje webhook.ping događaj na URL vaše krajnje točke.