Dokun API documentation
The Dokun API is for integrating your POS and your own systems. All requests use JSON over HTTPS. Base URL:
https://dokun.io/api/v1Plan requirement
The API and webhooks are available on the Pro plan only. The plan is checked on every request: if the trial has ended or payment failed, requests return 402; on non-Pro plans, 403. Webhooks are not sent.
Authentication
Create an API key in the business dashboard. Keys start with dk_ and are shown only once when created (we only store a hash). Send it on every request in the Authorization header using the Bearer scheme. The key is bound to your business and only accesses your own cards.
Authorization: Bearer dk_...Rate limit
At most 60 requests per minute per key. When exceeded, the API returns 429; the retry-after header gives the seconds until the next minute starts.
Error codes
Error response bodies are plain text, not JSON. Act on the status code.
| Code | Body | When |
|---|---|---|
| 400 | serial gerekli / geçersiz Idempotency-Key | serial missing or invalid Idempotency-Key |
| 401 | unauthorized | Missing or invalid key |
| 402 | payment required | Trial ended or subscription suspended |
| 403 | plan | Plan does not include the API (not Pro) |
| 404 | kart bulunamadı | Card not found or belongs to another business |
| 409 | istek işleniyor | A request with the same Idempotency-Key is still in progress |
| 429 | rate limit | Rate limit exceeded |
List customers
GET /api/v1/customers
Returns your business's customers, newest first. There is no pagination: the response contains at most 200 records (the 200 most recent sign-ups). marketing_consent_at is when marketing consent was given; null if not given.
curl https://dokun.io/api/v1/customers \
-H "Authorization: Bearer dk_..."
{ "customers": [ { "id": "…", "name": "Ayşe", "email": "ayse@example.com",
"created_at": "2026-10-30T09:12:00Z", "marketing_consent_at": null } ] }Add a stamp
POST /api/v1/stamp
Adds stamps to a card. serial is the card serial number or the full QR text scanned at the counter (send it as is). delta is optional, default 1. multiplier in the response is the factor applied by a campaign (e.g. double stamps). On success the card updates in Apple Wallet and the stamp webhook fires.
curl -X POST https://dokun.io/api/v1/stamp \
-H "Authorization: Bearer dk_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: fis-000123" \
-d '{"serial": "ABC123", "delta": 1}'
{ "ok": true, "balance": 4, "multiplier": 1 }Idempotency-Key
To retry safely after network errors, send the optional Idempotency-Key header (1–64 printable ASCII characters, e.g. the receipt number). The same key returns the same response for 24 hours and the stamp is not repeated; a replayed response carries the idempotent-replayed: true header. Only successful responses are stored; a failed request can be retried with the same key.
Webhooks
Add a webhook in the business dashboard by choosing an HTTPS URL and an event type. For each event we POST a JSON body to your URL. Body format: event (event name), data (event data), ts (Unix time in milliseconds).
| Event | data fields | When |
|---|---|---|
| signup | email, serial | New card sign-up. Contains personal data (email); store it securely. |
| stamp | serial, balance | Stamp from the counter or dashboard |
| stamp | serial, delta, multiplier, balance | Stamp via API |
| reward | serial, balance, target | Reward earned. Fired only for counter/dashboard stamps, not for API stamps. |
POST https://ornek.com/dokun-webhook
content-type: application/json
x-dokun-signature: 5d41402abc4b2a76…
x-dokun-attempt: 1
{ "event": "stamp", "data": { "serial": "ABC123", "balance": 4 }, "ts": 1761815520000 }Delivery and retries
Only a 2xx response counts as success; redirects (3xx) are not followed and requests time out after about 5 seconds. Failed deliveries are retried with exponential backoff (about 2 and 4 minutes later) up to 3 attempts in total; the x-dokun-attempt header gives the attempt number. Retries send the identical body: use event + ts + data to avoid double processing.
Signature verification
On every request, the x-dokun-signature header is the HMAC-SHA256 of the raw body keyed with your webhook secret (the value shown in the dashboard, used as is), hex-encoded. Verify against the raw text before parsing JSON and use a constant-time comparison. The signature has no timestamp; check the ts field against replays (e.g. reject events older than 10 minutes).
import { createHmac, timingSafeEqual } from 'node:crypto'
const raw = await req.text() // JSON.parse'tan önce ham gövde
const expected = createHmac('sha256', process.env.DOKUN_WEBHOOK_SECRET).update(raw).digest()
const got = Buffer.from(req.headers.get('x-dokun-signature') ?? '', 'hex')
if (got.length !== expected.length || !timingSafeEqual(got, expected)) return new Response(null, { status: 401 })
const { event, data, ts } = JSON.parse(raw)
if (Date.now() - ts > 10 * 60_000) return new Response(null, { status: 400 })Questions: WhatsApp