Webhooks
Webhooks
B’Webhooks tirċievi notifikazzjonijiet f’ħin reali meta jseħħu avvenimenti fil-Workspace tiegħek — eż. meta jiġi skannjat QR-Code.
Ħolqien ta’ Webhook
POST /v1/webhooks{ "url": "https://example.com/webhooks/qr3", "events": ["qr.scanned", "qr.created"], "secret": "my-secret-key-min-16-chars"}Rispons
{ "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"}Tipi ta’ Avvenimenti
| Event | Deskrizzjoni |
|---|---|
* | L-avvenimenti kollha |
qr.created | QR-Code maħluq |
qr.updated | QR-Code aġġornat (URL, status, tags) |
qr.deleted | QR-Code imħassar |
qr.scanned | QR-Code skannjat |
qr.flagged | QR-Code mmarkat bħala mhux sigur |
webhook.ping | Ping tat-test (permezz tal-endpoint /ping) |
Format tal-Payload
Il-payloads kollha tal-Webhook għandhom dan il-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" }}Avvenimenti tal-Kodiċi: qr.created u qr.updated
F’dawn iż-żewġ avvenimenti, data hija l-kodiċi eżattament kif ipprovdut minn GET /v1/codes/:id. Dan jinkludi appearance, il-kuluri ffrankati fuq il-kodiċi (null tfisser l-iswed jew l-abjad standard):
{ "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" }}L-eżempju huwa mqassar. Il-kampi kollha jinsabu fl-API tal-Kodiċijiet.
Verifika tal-Firma
qr3.app jiffirma kull talba (request) ta’ Webhook b’HMAC-SHA256:
X-QR3-Signature: sha256=a1b2c3d4e5f6...X-QR3-Event: qr.scannedX-QR3-Delivery: wdl_abc123X-QR3-Attempt: 1User-Agent: qr3-webhooks/1.0Implimenta l-Verifika
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)Loġika ta’ Retry
F’każ ta’ żbalji (HTTP >= 300 jew timeout), qr3.app jipprova jerġa’ jibgħat il-kunsinna:
| Prova | Dewmien |
|---|---|
| 1 | Minnufih |
| 2 | Minuta |
| 3 | 5 minuti |
Wara 10 żbalji konsekuttivi, il-Webhook jiġi ddiżattivat awtomatikament.
Garanzija tal-Kunsinna
qr3.app jikkunsinna mill-inqas darba, mhux eżattament darba. L-istess avveniment jista’ għalhekk jilħaq l-endpoint tiegħek aktar minn darba: bħala tentattiv mill-ġdid wara żball jew bħala ripetizzjoni ta’ tentattiv li diġà intbagħat.
- Neħħi d-duplikati bl-użu tal-
idtal-payload (evt_…). Jappartjeni lill-avveniment u jibqa’ l-istess f’kull tentattiv u f’kull ripetizzjoni.X-QR3-Deliveryjidentifika t-tentattiv individwali: ripetizzjoni ta’ dak it-tentattiv iġorr l-istess valur, tentattiv ġdid iġorr wieħed ġdid. Ikkonfermaidli diġà taf b’2xx, mhux b’4xx: 4xx jingħadd bħala tentattiv li falla u, wara 10 żbalji konsekuttivi, il-Webhook jiġi ddiżattivat awtomatikament. - Tentattiv li għadu
pendingsiegħa wara li jinħoloq (pereżempju għax il-messaġġ tiegħu fil-kju intilef) jiġi ssettjat fuqfailedminn tindif ta’ kuljum;status_codeuresponse_time_msjibqgħunull. Dan mhuwiex żball tal-endpoint tiegħek u ma jingħadx fost l-10 żbalji konsekuttivi. Il-log jaħżen għalih it-testDelivery was not completed in time — closed by cleanup; l-endpoint tal-logs tal-kunsinna ma jirritornahx. L-għeluq mhuwiex definittiv: jekk il-messaġġ xorta jasal, jiġi kkunsinnat u r-riżultat veru tiegħu jieħu post dak it-test.
Logs tal-Kunsinna
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" } ]}It-tentattivi ta’ kunsinna jinżammu għal 90 jum (il-jum UTC kurrenti flimkien mad-89 jum ta’ qabel) u mbagħad jitħassru awtomatikament. Minbarra l-oqsma murija, il-log jaħżen il-payload tal-avveniment u l-ewwel kilobyte (1 KB) tat-tweġiba tal-endpoint; dan l-endpoint ma jirritornax l-ebda wieħed minnhom.
Ping tat-Test
Ittestja Webhook minnufih:
POST /v1/webhooks/:id/pingDan jibgħat avveniment webhook.ping lill-URL tal-endpoint tiegħek.