Skip to content

Developers

QR code API and webhooks

Create and edit dynamic codes from your own systems, read their scan analytics, and hear about scans and form entries the moment they happen. JSON over HTTPS, on the Pro plan and above.

OpenAPI description for Postman, client generators and no-code tools.

Authentication

Create a key under Developers in the dashboard and send it as a bearer token. A key acts as the person who created it, with only the permissions it was given, and stops working if they leave the workspace. Keep keys on a server; a key in a web page or an app is a key anyone can use.

curl https://qr.div-systems.com/api/v1/codes \
  -H "Authorization: Bearer qrb_live_…"
Permissions a key can have
codes:readList and read codes
codes:writeCreate, edit, pause and archive dynamic codes
analytics:readRead daily scan totals
webhooks:writeSubscribe and unsubscribe webhook endpoints

Endpoints

Every path is relative to https://qr.div-systems.com/api/v1. Responses wrap the result in data.

RequestDoesNeeds
GET /codesList codes, newest firstcodes:read
POST /codesCreate a dynamic URL codecodes:write
GET /codes/{id}Get one codecodes:read
PATCH /codes/{id}Change name, destination or statuscodes:write
DELETE /codes/{id}Archive a codecodes:write
GET /codes/{id}/analyticsDaily scan totalsanalytics:read
GET /codes/{id}/imageThe code as a verified SVG or PNGcodes:read
GET /webhooksList webhook endpointswebhooks:write
POST /webhooksSubscribe an endpointwebhooks:write
DELETE /webhooks/{id}Unsubscribe an endpointwebhooks:write

Create a code

curl -X POST https://qr.div-systems.com/api/v1/codes \
  -H "Authorization: Bearer qrb_live_…" \
  -H "Content-Type: application/json" \
  -d '{"name":"Table 7","destination":"https://example.com/menu"}'

The response includes the short link's slug. Change the destination at any time with PATCH; every printed copy follows. Fetch /codes/{id}/image for a file: it is decoded before it is returned, like every download from QRBrand.

Pages of results

Lists return up to limit items, newest first, and a next_before timestamp. Pass it as before for the next page; it is null on the last one.

Errors and limits

Errors have a stable code to branch on and a message written for a person.

HTTP/1.1 409 Conflict
{ "error": { "code": "plan_limit", "message": "Your plan includes 500 dynamic codes and all of them are in use. …" } }

401 means the key is missing, unknown, revoked or expired; 403 that it lacks the permission or the plan no longer includes the API; 404 that nothing with that id is in your workspace; 409 that the plan is full; 422 that the request was understood but not acceptable. Each key may make 120 requests a minute; past that the answer is 429 with a Retry-After header. A code frozen because a plan lapsed still redirects, but cannot be edited until the plan is renewed.

Webhooks

Add an https endpoint under Developers, or subscribe one with POST /webhooks. We send a JSON POST for each event you choose. Deliveries are retried for about a day, and every attempt is listed in the dashboard, where any delivery can be sent again.

Events
code.createdA code was created
code.updatedA code’s name, destination or status changed
code.archivedA code was archived
code.scannedA person scanned a code (crawlers and link previews excluded)
form.submittedSomeone filled in a form on a landing page
bulk.completedA bulk upload finished
{
  "id": "evt_4f0c…",
  "type": "code.scanned",
  "created_at": "2026-09-18T12:00:00Z",
  "data": { "code_id": "…", "scanned_at": "…", "country": "GB", "device": "mobile", "os": "iOS" }
}

Delivery is at least once: use id to ignore a repeat. Scan events never include anything that identifies the person scanning.

Checking a delivery came from us

Each request carries a QRBrand-Signature header: t=<unix time>,v1=<HMAC-SHA256>, computed over the timestamp, a full stop and the raw body, with your endpoint's signing secret. Refuse anything that does not match or is more than five minutes old.

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

// body must be the raw request body, before any JSON parsing.
export function isFromQRBrand(header, body, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const timestamp = Number(parts.t);
  if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false; // older than 5 minutes

  const expected = createHmac('sha256', secret).update(`${timestamp}.${body}`).digest('hex');
  return parts.v1?.length === expected.length &&
    timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}
QR code API and webhooks | QRBrand