Webhooks
Event catalog
Every QRMax webhook is signed with HMAC-SHA256, retried with exponential backoff, and includes full context. Subscribe to any combination of events from your dashboard.
Delivery guarantees
HMAC-SHA256 signed
Every payload includes an X-QRMax-Signature header. Validate with your webhook secret before trusting.
Exponential retries
Failed deliveries retry at 5s, 30s, 2m, 10m, 1h, 6h, 24h. After 24h the event moves to the dead-letter log (retained 7 days).
At-least-once delivery
Events may be delivered more than once. Use the event.id field for idempotency.
QR code events
qr.createdFires when a new QR code is created via dashboard, API, or bulk import.
Trigger: Immediately after creation, synchronously with the API response.
▸Example payload
{
"event": "qr.created",
"createdAt": "2026-04-19T18:00:00Z",
"data": {
"id": "qr_01HNP...",
"type": "url",
"target": "https://example.com",
"dynamic": true,
"shortUrl": "https://qrx.io/a3b9c2",
"createdBy": "usr_01HNP..."
}
}qr.updatedFires when a dynamic QR code's destination, metadata, or design is modified.
Trigger: After a successful PATCH / PUT to /qr-codes/:id.
▸Example payload
{
"event": "qr.updated",
"createdAt": "2026-04-19T18:01:22Z",
"data": {
"id": "qr_01HNP...",
"before": { "target": "https://example.com/spring-sale" },
"after": { "target": "https://example.com/summer-sale" },
"updatedBy": "usr_01HNP..."
}
}qr.deletedFires when a QR code is permanently deleted.
Trigger: Immediately after DELETE /qr-codes/:id.
▸Example payload
{
"event": "qr.deleted",
"createdAt": "2026-04-19T18:30:00Z",
"data": {
"id": "qr_01HNP...",
"deletedBy": "usr_01HNP..."
}
}qr.scannedFires every time a dynamic QR is scanned. Batched up to 200 ms for high-volume codes.
Trigger: Within 200 ms of redirect.
▸Example payload
{
"event": "qr.scanned",
"createdAt": "2026-04-19T18:33:15Z",
"data": {
"qrId": "qr_01HNP...",
"scannedAt": "2026-04-19T18:33:15Z",
"country": "US",
"city": "San Francisco",
"device": "iPhone",
"os": "iOS 18.2",
"referer": null
}
}Campaign events
campaign.createdFires when a campaign group (with QR codes + schedule) is created.
Trigger: After POST /campaigns.
▸Example payload
{
"event": "campaign.created",
"data": {
"id": "cmp_01HNP...",
"name": "Spring Sale 2026",
"startDate": "2026-04-01",
"endDate": "2026-05-31",
"qrCodeIds": ["qr_01", "qr_02", "qr_03"]
}
}campaign.endedFires once at the scheduled end of a campaign.
Trigger: When endDate has passed (within 5 minutes).
▸Example payload
{
"event": "campaign.ended",
"data": {
"id": "cmp_01HNP...",
"totalScans": 12340,
"uniqueScanners": 8721,
"conversionRate": 0.028
}
}Billing events
subscription.createdFires when a workspace starts a paid plan.
Trigger: After successful checkout.
▸Example payload
{
"event": "subscription.created",
"data": {
"workspaceId": "ws_01HNP...",
"plan": "pro",
"interval": "month",
"amount": 29.00,
"currency": "USD"
}
}subscription.updatedFires on plan upgrade, downgrade, or interval change.
Trigger: After PATCH /billing/subscription.
▸Example payload
{
"event": "subscription.updated",
"data": {
"workspaceId": "ws_01HNP...",
"before": { "plan": "starter" },
"after": { "plan": "pro" }
}
}subscription.canceledFires when a paid plan is canceled (end of current period).
Trigger: Immediately on cancellation; grace period until period end.
▸Example payload
{
"event": "subscription.canceled",
"data": {
"workspaceId": "ws_01HNP...",
"effectiveAt": "2026-05-19T23:59:59Z",
"reason": "user-initiated"
}
}Team events
member.invitedFires when a team member is invited to a workspace.
Trigger: After POST /team/invites.
▸Example payload
{
"event": "member.invited",
"data": {
"workspaceId": "ws_01HNP...",
"email": "teammate@example.com",
"role": "editor",
"invitedBy": "usr_01HNP..."
}
}member.joinedFires when an invited member accepts.
Trigger: After accepting invite email.
▸Example payload
{
"event": "member.joined",
"data": {
"workspaceId": "ws_01HNP...",
"userId": "usr_01HNP...",
"role": "editor"
}
}Set up your first webhook
Configure endpoint URLs, event filters, and HMAC secrets in the dashboard.
Open webhook settings