API & webhooks
Manage dynamic QR codes from your own systems, and hear about scans, card views and leads the moment they happen. Both are part of OneQR Pro.
Authentication
Create a key in Settings → API keys and send it as a bearer token. Keys act on your own workspace; keep them secret and revoke any that leak.
curl https://oneqr.me/api/v1/codes \
-H "Authorization: Bearer oqr_your_key"Successful responses are { "data": … }; failures are { "error": { "message": "…" } } with a 4xx or 5xx status. Each key can make 120 requests a minute.
Endpoints
Base URL: https://oneqr.me/api/v1
| GET | /codes | List every dynamic code in your workspace, newest first, with plan usage. |
| POST | /codes | Create a code. `id` is optional — we pick one if you leave it out. |
| GET | /codes/{id} | Get one code. |
| PATCH | /codes/{id} | Change any fields, including where it points. Printed codes follow. |
| DELETE | /codes/{id} | Delete a code and its analytics. Printed copies stop working. |
| GET | /codes/{id}/stats?days=30 | Scans, unique visitors, countries, devices, browsers and referrers for 7, 30, 90 or 365 days. |
| GET | /codes/{id}/qr?format=png&size=512 | The code as a PNG or SVG image in its saved colours. |
Create a code
curl https://oneqr.me/api/v1/codes \
-H "Authorization: Bearer oqr_your_key" \
-H "Content-Type: application/json" \
-d '{
"id": "spring-menu",
"type": "url",
"values": { "url": "https://example.com/menu" },
"name": "Spring menu",
"folder": "Restaurant"
}'Other fields: status (active or paused), expiresAt (milliseconds or null), password (empty string removes it), rules (ios, android, and countries as [{ "country": "IN", "to": "…" }]) and design (colours and styles). Types are url, whatsapp (phone, message), location and page for hosted pages.
{
"data": {
"id": "spring-menu",
"to": "https://example.com/menu",
"type": "url",
"values": { "url": "https://example.com/menu" },
"name": "Spring menu",
"folder": "Restaurant",
"status": "active",
"scans": 0,
"hasPassword": false,
"createdAt": 1791860000000
}
}Webhooks
Add an https endpoint in Settings → Webhooks and pick events. We POST JSON within seconds and wait up to 5 seconds for a 2xx reply. A failed delivery is retried with growing gaps for up to 24 hours; after 20 failed attempts in a row the webhook switches off until you turn it back on. Each delivery carries an id, so you can ignore repeats.
qr.scanned— A dynamic QR code or hosted page was scannedprofile.viewed— Someone opened one of your digital business cardslead.captured— A visitor left their details on your cardorder.created— A customer placed an order from one of your catalogscontact.created— Someone sent a message through a landing page's contact form
POST /your-endpoint
OneQR-Event: qr.scanned
OneQR-Signature: t=1791860000,v1=5f2b…
{
"id": "evt_8c1d…",
"type": "qr.scanned",
"created_at": "2026-10-13T08:15:00.000Z",
"data": {
"code_id": "spring-menu",
"scanned_at": "2026-10-13T08:15:00.000Z",
"country": "IN",
"city": "Mumbai",
"os": "Android",
"device": "mobile",
"browser": "Chrome",
"referrer": null,
"unique": true
}
}Check the signature
Sign `${t}.${rawBody}` with your webhook's secret using HMAC-SHA256 and compare it with v1. Reject events older than a few minutes.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return v1.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}