Webhooks
Webhooks
Webhooks deliver real-time notifications when events occur in your workspace — for example when a QR code is scanned.
Create a Webhook
POST /v1/webhooks{ "url": "https://example.com/webhooks/qr3", "events": ["qr.scanned", "qr.created"], "secret": "my-secret-key-min-16-chars"}Response
{ "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"}Available Events
| Event | Trigger |
|---|---|
* | All events (wildcard) |
qr.created | New QR code created |
qr.updated | QR code updated (URL, status, tags) |
qr.deleted | QR code deleted |
qr.scanned | QR code scanned |
qr.flagged | QR code flagged as unsafe by Web Risk |
scan.created | New scan event (same as qr.scanned, higher granularity) |
workspace.updated | Workspace settings changed |
Signature Verification
Every delivery includes the X-QR3-Signature header. Verify it with HMAC-SHA256:
import crypto from 'node:crypto';
function verifySignature(secret: string, body: string, signature: string): boolean { const expected = 'sha256=' + crypto .createHmac('sha256', secret) .update(body) .digest('hex'); return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));}import hmac, hashlib
def verify_signature(secret: str, body: str, signature: str) -> bool: expected = 'sha256=' + hmac.new( secret.encode(), body.encode(), hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected)Retry Policy
Failed deliveries are retried 3 times with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 60 seconds |
| 3 | 300 seconds (5 min) |
After 3 failures the webhook is automatically deactivated (is_active: false).
List Deliveries
GET /v1/webhooks/:id/deliveries?limit=20Returns the delivery history with status, status code, and response time.
Test a Webhook
POST /v1/webhooks/:id/pingSends a webhook.ping test event to verify the endpoint is reachable.