Webhooks
Webhooks
Με τα Webhooks λαμβάνετε ειδοποιήσεις σε πραγματικό χρόνο όταν προκύπτουν συμβάντα στον χώρο εργασίας σας — π.χ. όταν σαρώνεται ένας κωδικός QR.
Δημιουργία Webhook
POST /v1/webhooks{ "url": "https://example.com/webhooks/qr3", "events": ["qr.scanned", "qr.created"], "secret": "my-secret-key-min-16-chars"}Απόκριση
{ "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"}Τύποι συμβάντων
| Συμβάν | Περιγραφή |
|---|---|
* | Όλα τα συμβάντα |
qr.created | Ο κωδικός QR δημιουργήθηκε |
qr.updated | Ο κωδικός QR ενημερώθηκε (URL, κατάσταση, ετικέτες) |
qr.deleted | Ο κωδικός QR διαγράφηκε |
qr.scanned | Ο κωδικός QR σαρώθηκε |
qr.flagged | Ο κωδικός QR επισημάνθηκε ως μη ασφαλής |
webhook.ping | Δοκιμαστικό ping (μέσω του τελικού σημείου /ping) |
Μορφή Payload
Όλα τα payload των Webhook έχουν αυτή τη μορφή:
{ "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" }}Συμβάντα κωδικών: qr.created και qr.updated
Σε αυτά τα δύο συμβάντα, το data είναι ο κωδικός ακριβώς όπως τον επιστρέφει το GET /v1/codes/:id. Σε αυτόν περιλαμβάνεται το appearance, δηλαδή τα χρώματα που είναι αποθηκευμένα στον κωδικό (το null αντιπροσωπεύει το προεπιλεγμένο μαύρο ή λευκό):
{ "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" }}Το παράδειγμα είναι συντομευμένο. Όλα τα πεδία είναι διαθέσιμα στο API κωδικών.
Επαλήθευση υπογραφής
Το qr3.app υπογράφει κάθε αίτημα Webhook με HMAC-SHA256:
X-QR3-Signature: sha256=a1b2c3d4e5f6...X-QR3-Event: qr.scannedX-QR3-Delivery: wdl_abc123X-QR3-Attempt: 1User-Agent: qr3-webhooks/1.0Υλοποίηση επαλήθευσης
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)Λογική επαναπροσπάθειας
Σε περίπτωση σφαλμάτων (HTTP >= 300 ή λήξη χρονικού ορίου), το qr3.app προσπαθεί ξανά να παραδώσει το αίτημα:
| Προσπάθεια | Καθυστέρηση |
|---|---|
| 1 | Αμέσως |
| 2 | 1 λεπτό |
| 3 | 5 λεπτά |
Μετά από 10 συνεχόμενα σφάλματα, το Webhook απενεργοποιείται αυτόματα.
Εγγύηση παράδοσης
Το qr3.app παραδίδει τουλάχιστον μία φορά, όχι ακριβώς μία. Το ίδιο συμβάν μπορεί επομένως να φτάσει στο τελικό σας σημείο περισσότερες από μία φορές: ως επαναπροσπάθεια μετά από σφάλμα ή ως επανάληψη μιας απόπειρας που έχει ήδη σταλεί.
- Αφαιρέστε τα διπλότυπα με το
idτου payload (evt_…). Ανήκει στο συμβάν και παραμένει το ίδιο σε κάθε απόπειρα και σε κάθε επανάληψη. ΤοX-QR3-Deliveryπροσδιορίζει τη μεμονωμένη απόπειρα: μια επανάληψη της απόπειρας αυτής φέρει την ίδια τιμή, μια νέα απόπειρα νέα. Επιβεβαιώστε ένα ήδη γνωστόidμε 2xx και όχι με 4xx: ένα 4xx μετράει ως αποτυχημένη απόπειρα και μετά από 10 συνεχόμενα σφάλματα το Webhook απενεργοποιείται αυτόματα. - Μια απόπειρα που είναι ακόμη
pendingμία ώρα μετά τη δημιουργία της (για παράδειγμα επειδή χάθηκε το μήνυμά της στην ουρά) τίθεται σεfailedαπό έναν ημερήσιο καθαρισμό· ταstatus_codeκαιresponse_time_msπαραμένουνnull. Αυτό δεν είναι σφάλμα του τελικού σας σημείου και δεν προσμετράται στα 10 συνεχόμενα σφάλματα. Το αρχείο καταγραφής αποθηκεύει γι’ αυτήν το κείμενοDelivery was not completed in time — closed by cleanup· το endpoint των καταγραφών παράδοσης δεν το επιστρέφει. Το κλείσιμο δεν είναι οριστικό: αν το μήνυμα φτάσει τελικά, παραδίδεται και το πραγματικό του αποτέλεσμα αντικαθιστά αυτό το κείμενο.
Καταγραφές παράδοσης
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" } ]}Οι απόπειρες παράδοσης διατηρούνται για 90 ημέρες (την τρέχουσα ημέρα UTC και τις 89 προηγούμενες) και στη συνέχεια διαγράφονται αυτόματα. Εκτός από τα πεδία που εμφανίζονται, το αρχείο καταγραφής αποθηκεύει το payload του συμβάντος και το πρώτο kilobyte (1 KB) της απόκρισης του endpoint· αυτό το endpoint δεν επιστρέφει κανένα από τα δύο.
Δοκιμαστικό Ping
Δοκιμάστε ένα Webhook αμέσως:
POST /v1/webhooks/:id/pingΑυτό στέλνει ένα συμβάν webhook.ping στη διεύθυνση URL του τελικού σας σημείου.