Back to docs

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.created

Fires 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.updated

Fires 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.deleted

Fires 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.scanned

Fires 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.created

Fires 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.ended

Fires 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.created

Fires 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.updated

Fires 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.canceled

Fires 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.invited

Fires 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.joined

Fires 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