Webhooks
Webhooks
S spletnimi kljukami (webhooks) prejemate obvestila v realnem času, ko se v vaši delovni površini (workspace) zgodijo dogodki — npr. ko je skenirana QR-koda.
Ustvarjanje spletne kljuke
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 dogodkov
| Dogodek | Opis |
|---|---|
* | Vsi dogodki |
qr.created | QR-koda ustvarjena |
qr.updated | QR-koda posodobljena (URL, status, oznake) |
qr.deleted | QR-koda izbrisana |
qr.scanned | QR-koda skenirana |
qr.flagged | QR-koda označena kot nevarna |
webhook.ping | Testni ping (preko končne točke /ping) |
Format podatkov (Payload)
Vsi podatki spletnih kljuk (payloads) imajo naslednjo obliko:
{ "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" }}Dogodki kode: qr.created in qr.updated
Pri teh dveh dogodkih je data koda natanko takšna, kot jo vrne GET /v1/codes/:id. To vključuje appearance, barve, shranjene v kodi (null predstavlja privzeto črno oziroma belo):
{ "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" }}Primer je skrajšan. Vsa polja so na voljo v API-ju za kode.
Preverjanje podpisa
qr3.app vsako zahtevo spletne kljuke podpiše s HMAC-SHA256:
X-QR3-Signature: sha256=a1b2c3d4e5f6...X-QR3-Event: qr.scannedX-QR3-Delivery: wdl_abc123X-QR3-Attempt: 1User-Agent: qr3-webhooks/1.0Implementacija preverjanja
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 }); },);import hmacimport hashlib
def verify_signature(payload: str, signature: str, secret: str) -> bool: expected = hmac.new( secret.encode(), payload.encode(), hashlib.sha256 ).hexdigest() received = (signature or "").removeprefix("sha256=") return hmac.compare_digest(expected, received)Logika ponovnih poskusov
V primeru napak (HTTP >= 300 ali časovna omejitev) qr3.app ponovno poskusi z dostavo:
| Poskus | Zakasnitev |
|---|---|
| 1 | Takoj |
| 2 | 1 minuta |
| 3 | 5 minut |
Po 10 zaporednih napakah se spletna kljuka samodejno onemogoči.
Zagotovilo o dostavi
qr3.app dostavlja vsaj enkrat, ne natančno enkrat. Isti dogodek lahko zato na vašo končno točko prispe večkrat: kot ponovni poskus po napaki ali kot ponovitev poskusa, ki je bil že poslan.
- Podvojene dogodke odpravite s poljem
idv payloadu (evt_…). Pripada dogodku in ostane enak pri vsakem poskusu in vsaki ponovitvi.X-QR3-Deliveryoznačuje posamezen poskus: ponovitev tega poskusa nosi isto vrednost, nov poskus novo. Že znaniidpotrdite s kodo 2xx, ne s 4xx: 4xx se šteje kot neuspel poskus, po 10 zaporednih napakah pa se spletna kljuka samodejno onemogoči. - Poskus, ki je uro po ustvarjanju še vedno
pending(na primer ker se je njegovo sporočilo v čakalni vrsti izgubilo), ga dnevno čiščenje nastavi nafailed;status_codeinresponse_time_msostanetanull. To ni napaka vaše končne točke in se ne všteva med 10 zaporednih napak. Dnevnik zanj shrani besediloDelivery was not completed in time — closed by cleanup; končna točka za dnevnike dostave ga ne vrne. Zaprtje ni dokončno: če sporočilo vseeno prispe, se dostavi, njegov dejanski rezultat pa to besedilo nadomesti.
Dnevniki dostave
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" } ]}Poskusi dostave se hranijo 90 dni (tekoči dan UTC in 89 prejšnjih dni), nato se samodejno izbrišejo. Poleg prikazanih polj dnevnik shranjuje payload dogodka in prvi kilobajt (1 KB) odgovora končne točke; ta končna točka ne vrača nobenega od obeh.
Testni ping
Takoj preizkusite spletno kljuko:
POST /v1/webhooks/:id/pingTo pošlje dogodek webhook.ping na vaš URL končne točke.