Kassa-API
Voor kassaleveranciers en ontwikkelaars. Met deze API stuurt een kassa haar verkopen naar Frequent: de klant krijgt vanzelf stempels of punten, een terugbetaling neemt ze terug, en aan de toog zie je de kaart van de klant.
Sleutel
De zaak maakt een sleutel per kassa in haar dashboard, onder Kassakoppeling, Andere kassa of kassaleverancier. Stuur hem mee in elke aanvraag:
Authorization: Bearer frq_live_…
Hoogstens 300 aanvragen per minuut per sleutel. Antwoorden zijn JSON.
De klant herkennen
Geef één van deze drie mee. card_code is wat de scanner leest van de QR-code op de kaart in Wallet (een adres of de 16 tekens van de kaart); e-mail en gsm vinden een klant die zijn kaart niet bij heeft.
card_codeemailphone(elke schrijfwijze: 0470 12 34 56 of +32 470…)
Een verkoop
Na elke betaalde bon. Bij een stempelkaart geeft elke verkoop één stempel; bij punten telt het bedrag. transaction_id is het nummer van de bon: dezelfde bon twee keer sturen geeft maar één keer iets. Zet reward_redeemed op true als de kassier de beloning als korting aansloeg.
curl -X POST https://frequent.be/api/v1/sales \
-H "Authorization: Bearer frq_live_…" \
-H "Content-Type: application/json" \
-d '{ "transaction_id": "BON-1043", "amount": 12.50, "card_code": "k7m2qz9xw4hp3trb" }'{
"outcome": "awarded",
"message": { "nl": "Verkoop geteld.", "fr": "Vente comptée." },
"cards": [{
"id": "k7m2qz9xw4hp3trb",
"customer": { "name": "Sofie Claes" },
"program": { "name": "Koffiekaart", "mechanic": "stamps", "goal": 10, "reward": "Gratis koffie" },
"balance": 7,
"reward_ready": false
}]
}Mogelijke uitkomsten: awarded, ignored (kaart vol of bedrag te klein), no_match (geen kaart voor die klant), no_customer (geen klant meegegeven). Een herhaalde bon geeft "duplicate": true.
Een terugbetaling
Neemt terug wat de oorspronkelijke verkoop gaf, nooit onder nul.
curl -X POST https://frequent.be/api/v1/refunds \
-H "Authorization: Bearer frq_live_…" \
-H "Content-Type: application/json" \
-d '{ "transaction_id": "RET-88", "refund_of": "BON-1043" }'De kaart opvragen
Om aan de toog te tonen of de beloning klaarstaat.
curl "https://frequent.be/api/v1/cards?card_code=k7m2qz9xw4hp3trb" \ -H "Authorization: Bearer frq_live_…"
Elke kaart heeft een lijst rewards: de beloning op de kaart ("id": "main") en eventuele extra beloningen, met hun kost en of ze nu kunnen.
Een beloning inwisselen
De kassier kiest een beschikbare beloning; de kassa zet de korting op de bon en meldt het ons. Twee kassa's tegelijk kunnen dezelfde beloning niet twee keer inwisselen.
curl -X POST https://frequent.be/api/v1/redemptions \
-H "Authorization: Bearer frq_live_…" \
-H "Content-Type: application/json" \
-d '{ "transaction_id": "BON-1044", "card_code": "k7m2qz9xw4hp3trb", "reward_id": "main" }'Antwoord: "outcome": "redeemed" met de beloning en de kaart zoals ze nu is. Te weinig stempels of punten geeft 409.
Betalen met een cadeaubon
De scanner leest de QR-code op de cadeaubon (of de code wordt getypt). Eerst het saldo, dan het bedrag afboeken:
curl "https://frequent.be/api/v1/giftcards?code=CB7K2M9QXW" \
-H "Authorization: Bearer frq_live_…"
curl -X POST https://frequent.be/api/v1/giftcards/spend \
-H "Authorization: Bearer frq_live_…" \
-H "Content-Type: application/json" \
-d '{ "transaction_id": "BON-1045", "code": "CB7K2M9QXW", "amount": 18.50 }'Nooit meer dan het saldo (anders 409), niet na de vervaldatum, en dezelfde bon twee keer boekt maar één keer af.
Webhooks
Wil je het meteen weten als er iets met een kaart gebeurt, ook als het niet via jouw kassa ging? De zaak voegt in het dashboard (Kassakoppeling, Webhooks) jouw webadres toe en kiest de gebeurtenissen. Wij sturen dan per gebeurtenis een POST met JSON.
card.created: een klant kreeg een kaartcard.updated: stempels of punten erbij of erafreward.redeemed: een beloning werd ingewisseldgiftcard.used: er werd met een cadeaubon betaald
POST https://jouw-systeem.be/frequent
Frequent-Event: card.updated
Frequent-Delivery: 5f0c…
Frequent-Signature: t=1762700000,v1=3b1f…
{
"id": "5f0c…",
"type": "card.updated",
"created_at": "2026-11-10T09:14:03Z",
"data": {
"card": { "id": "k7m2qz9xw4hp3trb", "program_name": "Koffiekaart", "mechanic": "stamps", "stamps": 7, "points": 0, "goal": 10 },
"customer": { "id": "…", "name": "Sofie Claes", "email": "sofie@example.com", "phone": "+32470123456" },
"activity": { "kind": "stamp", "delta": 1, "amount": 4.5, "source": "counter" }
}
}Controleer elke keer de handtekening: bereken HMAC-SHA256 van <t>.<body> met de geheime code (whsec_…) uit het dashboard en vergelijk met v1. Weiger berichten ouder dan 5 minuten.
import crypto from "node:crypto";
function fromFrequent(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 = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}Antwoord binnen 8 seconden met een 2xx. Lukt dat niet, dan proberen we opnieuw, ten vroegste na 1 minuut, 5 minuten, 30 minuten, 2, 6, 12 en 24 uur (en minstens één keer per dag). Na 25 mislukte pogingen op rij zetten we de webhook uit; de zaak kan hem weer aanzetten. Een bericht kan uitzonderlijk twee keer komen: gebruik id om dubbels over te slaan.
Fouten
Een fout komt terug als { "error": { "code": "…", "message": { "nl": "…", "fr": "…" } } }: 401 (sleutel), 403 (abonnement van de zaak afgelopen), 404, 422 (veld), 429 (te veel aanvragen).
Vragen
Bouw je een koppeling voor jouw kassa? Mail ons op viktor@frequent.be.