dokun

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/v1

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

CodeBodyWhen
400serial gerekli / geçersiz Idempotency-Keyserial missing or invalid Idempotency-Key
401unauthorizedMissing or invalid key
402payment requiredTrial ended or subscription suspended
403planPlan does not include the API (not Pro)
404kart bulunamadıCard not found or belongs to another business
409istek işleniyorA request with the same Idempotency-Key is still in progress
429rate limitRate 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).

Eventdata fieldsWhen
signupemail, serialNew card sign-up. Contains personal data (email); store it securely.
stampserial, balanceStamp from the counter or dashboard
stampserial, delta, multiplier, balanceStamp via API
rewardserial, balance, targetReward 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