Skip to main content
QRelix
Browse documentation

QR Code Webhooks and Zapier

Updated

QR code webhooks push a signed JSON message to a URL you choose every time something happens to one of your dynamic QR codes. Use them to log scans in a spreadsheet, alert a Slack channel, update a CRM, or trigger any workflow in Zapier or Make. Every delivery is retried on failure and recorded in a delivery log you can inspect from the dashboard.

What a webhook gives you

  • Instant scan notifications. A qr.scanned event is sent as soon as a scan is recorded, before the visitor even lands on your destination.
  • Change tracking. Events fire when a QR code is created, updated, deleted, paused, or resumed.
  • Signed payloads. Each request carries an HMAC-SHA256 signature so your receiver can prove it came from QRelix.
  • Automatic retries. A failed delivery is retried up to six times over roughly ten hours.
  • A delivery log. Every attempt, response code, and error is kept for 30 days and can be resent with one click.

Webhooks are available on every plan. Free accounts are subject to the same 50-scan cap per QR code as the rest of the product.

Events

Event When it fires
qr.scanned A QR code was scanned. Includes the QR code and the scan (device, browser, country, destination).
qr.created A new QR code was created.
qr.updated A QR code's name, destination, or settings changed. Includes changedFields.
qr.deleted A QR code was deleted.
qr.status_changed A QR code was paused, resumed, or expired. Includes previousStatus and status.
webhook.test Sent on demand from the dashboard to check an endpoint.

Add an endpoint

  1. Open Dashboard and choose Integrations.
  2. Click Add endpoint, paste the https:// URL that should receive events, and tick the events you want.
  3. Optionally limit the endpoint to one QR code. Leave it on All QR codes to receive everything.
  4. Copy the signing secret shown after saving. It stays available on the endpoint page.
  5. Click Send test event to confirm your receiver answers with a 2xx status.

Endpoints must use HTTPS and resolve to a public address. Local and private network addresses are rejected.

Payload format

Every delivery is a POST with Content-Type: application/json. The body is an envelope with a stable event id, the event type, and the event data.

{
  "id": "evt_8mB3xZk2R1qf",
  "type": "qr.scanned",
  "apiVersion": "2026-10-12",
  "createdAt": "2026-10-12T15:04:05.000Z",
  "data": {
    "qrCode": {
      "id": "clz0example0000qrcode",
      "name": "Spring menu",
      "type": "URL",
      "status": "ACTIVE",
      "destinationUrl": "https://example.com/menu",
      "scanCount": 128,
      "landingUrl": "https://qrelix.com/landing/clz0example0000qrcode",
      "dashboardUrl": "https://qrelix.com/dashboard/qr-codes/clz0example0000qrcode/logs"
    },
    "scan": {
      "id": "clz0example0000scanlog",
      "scannedAt": "2026-10-12T15:04:05.000Z",
      "country": "US",
      "browser": "Safari",
      "os": "iOS",
      "device": "iPhone",
      "isMobile": true,
      "destinationUrl": "https://example.com/menu"
    }
  }
}

The same event id is reused for every retry and for every endpoint the event is sent to, so you can deduplicate on id.

Request headers

Header Meaning
X-QRelix-Signature t=<unix seconds>,v1=<hex HMAC> (see below).
X-QRelix-Event The event type, for routing without parsing the body.
X-QRelix-Event-Id The envelope id.
X-QRelix-Delivery-Id Unique per attempt chain, useful when reading the delivery log.
X-QRelix-Attempt 1 for the first try, then 2, 3, and so on.

Verify the signature

The signature is HMAC-SHA256(secret, "<timestamp>.<raw body>"), hex encoded. Compute it over the raw request body exactly as received, compare in constant time, and reject timestamps older than five minutes.

import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyQRelixWebhook(secret, rawBody, signatureHeader) {
  const parts = Object.fromEntries(signatureHeader.split(",").map((p) => p.split("=")));
  const timestamp = Number(parts.t);
  if (!timestamp || Math.abs(Date.now() / 1000 - timestamp) > 300) return false;

  const expected = createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
  const received = parts.v1 || "";
  return received.length === expected.length && timingSafeEqual(Buffer.from(received, "hex"), Buffer.from(expected, "hex"));
}

When you rotate a secret from the endpoint page, deliveries made from that moment on are signed with the new one.

Retries and the delivery log

A delivery succeeds when your endpoint answers with any 2xx status within 10 seconds. Redirects, other status codes, timeouts, and connection errors count as failures and are retried after 1 minute, 5 minutes, 30 minutes, 2 hours, and 8 hours.

If ten deliveries in a row use up all their retries, the endpoint is paused automatically and the reason is shown on its page. Fix the receiver, then click Resume endpoint.

The endpoint page lists every delivery with its status, response code, duration, and attempt count. Expand a row to see the exact request body and the response your server returned, and use Resend to deliver it again.

Connect Zapier or Make

  1. On the Integrations page, create an API key and copy it.
  2. In Zapier or Make, add the QRelix app and paste the key as the connection.
  3. Choose a trigger such as New scan. The app registers a webhook endpoint for you; it appears in your endpoint list labelled with its source.

The Zapier and Make apps use the public API below, so anything they can do you can also do from a script.

Public API

Authenticate with Authorization: Bearer qrx_... using an API key from the Integrations page. Responses are JSON; errors use { "error": { "message", "code" } }.

Method Endpoint Purpose
GET /api/v1/me The account the key belongs to.
GET /api/v1/qr-codes List QR codes. Supports limit, cursor, and status.
GET /api/v1/qr-codes/{id} One QR code.
GET /api/v1/qr-codes/{id}/scans Recent scans for a QR code.
GET /api/v1/events Event catalogue with sample payloads.
GET /api/v1/webhooks List webhook endpoints.
POST /api/v1/webhooks Create an endpoint: { "url", "events": ["qr.scanned"], "qrCodeId" }. Returns the secret once.
PATCH /api/v1/webhooks/{id} Update an endpoint.
DELETE /api/v1/webhooks/{id} Delete an endpoint.

Keys are limited to 120 requests per minute.

Frequently asked questions

Does a webhook slow down the scan? No. The event is queued while the visitor is redirected and delivered after the redirect is sent.

Can I receive events for only one QR code? Yes. Set Only for to a single QR code when adding or editing the endpoint, or pass qrCodeId through the API.

How many endpoints can I have? Up to 20 endpoints and 10 API keys per account.

What happens to events while an endpoint is paused? They are not queued. Resume the endpoint to receive new events; events from the paused period are not replayed.

Create your first trackable QR code in a minute

Free plan, no credit card. Edit the destination any time and watch the scans come in.