Kassen-Connect API
Binde ein Kassensystem in unter einer Stunde an: automatisch stempeln beim Bezahlen, Belohnungen an der Kasse einlösen, Stornos und Tagesabgleich — über eine kleine, stabile REST-API.
Fertige Anbindungen: ready2order, Shopify (Kasse wie Online-Shop), helloCash und KORONA.pos verbindet der Händler ohne eine Zeile Code direkt im Dashboard.
Schnellstart
- Der Ladenbesitzer erstellt im Dashboard unter Kassen-Connect einen POS-Token (
mw_pos_…) — pro Kasse bzw. Filiale ein eigener Token. - Deine Kasse ruft nach jedem Bezahlvorgang
POST /v1/pos/transactionmit Seriennummer (QR der Kundenkarte), Bon-Betrag und Bon-Nr. auf. - Fertig — Stempel-Regeln (1/Bon oder nach Umsatz, Deckel) pflegt der Händler selbst im Dashboard. Zum Testen gibt es dort einen eingebauten Simulator gegen die echte API.
curl -X POST https://midaswallet.de/api/v1/pos/transaction \
-H "Authorization: Bearer mw_pos_DEIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"passId": "MW-1234-5678",
"amountCents": 2350,
"externalRef": "BON-2026-000482"
}'Basis-URL: https://midaswallet.de/api · Alle Antworten sind JSON (UTF-8). Beträge immer in Cent.
Authentifizierung
Jeder Request trägt den POS-Token — wahlweise in einem von zwei Headern (für Kassensysteme, die den Authorization-Header nicht frei setzen können):
Authorization: Bearer mw_pos_…X-Pos-Key: mw_pos_…- Tokens sind pro Laden (und optional pro Filiale) — sie sehen nie Daten anderer Läden.
- Der Händler kann Tokens jederzeit im Dashboard widerrufen; widerrufene Tokens antworten mit
401. - Mit
GET /v1/pos/pingvalidierst du Token + Verbindung, bevor der Händler das Feature aktiviert hat.
Sandbox & Testen
Für die Entwicklung erstellt der Ladenbesitzer im Dashboard einen Sandbox-Token (mw_pos_test_…). Er nutzt exakt dieselben Endpunkte wie ein echter Token, aber:
- Er bucht ausschließlich auf die auto-provisionierte Test-Karte des Ladens — die Seriennummer liefert
GET /v1/pos/pingim FeldtestPass. Echte Kundenkarten sind für Sandbox-Token unsichtbar. - Er funktioniert vor dem Go-Live: der Aktivierungs-Schalter des Händlers wird übersprungen.
- Test-Buchungen laufen in einem eigenen Idempotenz-Raum und tauchen weder in der Umsatz-Statistik des Händlers noch im Abgleich echter Tokens auf — eine in der Sandbox verbrauchte Bon-Nr. blockiert nie die spätere echte Buchung.
GET /v1/pos/transactionsundGET /v1/pos/summaryzeigen einem Sandbox-Token nur seine Sandbox-Bewegungen.
Zum manuellen Ausprobieren gibt es im Händler-Dashboard außerdem einen Simulator gegen die echte API.
Endpunkte
/v1/pos/pingVerbindungs- & Token-Test
Prüft Token und Verbindung, liefert Laden, Kassen-Standort und die aktive Stempel-Regel. Funktioniert auch, bevor der Händler Kassen-Connect scharf schaltet (Status steht in config.enabled) — ideal für den Einrichtungs-Assistenten deiner Kasse.
{
"ok": true,
"apiVersion": "1.3",
"serverTime": "2026-07-06T14:03:11.000Z",
"merchant": { "id": "m_abc123", "name": "Döner-Palast" },
"location": { "id": "loc_1", "name": "Hauptfiliale" },
"sandbox": false,
"testPass": null,
"config": { "enabled": true, "centsPerStamp": 500, "maxStampsPerTransaction": 10 }
}/v1/pos/pass/{passId}Kundenkarte abfragen (Kassendisplay)
Liest Saldo, fehlende Stempel und Belohnungs-Status, ohne etwas zu buchen — z. B. um an der Kasse „Noch 2 Stempel bis zur Belohnung“ anzuzeigen. passId akzeptiert die gescannte Seriennummer (QR-Inhalt) oder die interne Pass-ID.
{
"passId": "p_xyz789",
"serialNumber": "MW-1234-5678",
"status": "ACTIVE",
"customerName": "Max M.",
"balance": 8,
"pointsRequiredForReward": 10,
"stampsToReward": 2,
"rewardAvailable": false,
"rewardDescription": "1 Döner gratis",
"stage": null,
"tier": { "level": 2, "name": "Silber" },
"lifetimeStamps": 43,
"lastVisitAt": "2026-06-28T11:42:00.000Z"
}/v1/pos/pass?phone=…Kundenkarte ohne Scan abfragen (Handynummer/E-Mail)
Neu in v1.4: Der Kunde nennt an der Kasse seine Handynummer (oder E-Mail), statt den Karten-QR zu zeigen — ?phone= oder ?email=. Telefonnummern dürfen in jeder üblichen Schreibweise kommen (serverseitig E.164-normalisiert, ohne Ländervorwahl gilt +49); aufgelöst wird die neueste aktive Karte des Ladens. Dieselben Felder customerPhone/customerEmail funktionieren auch direkt in transaction, transactions/batch und redeem als Ersatz für passId.
{
"passId": "p_xyz789",
"serialNumber": "MW-1234-5678",
"status": "ACTIVE",
"customerName": "Max M.",
"balance": 8,
"stampsToReward": 2,
"rewardAvailable": false
}/v1/pos/enrollKarte direkt an der Kasse ausstellen (v1.6)
Schließt die Lücke „Kunde hat noch keine Karte": Handynummer (oder E-Mail) rein, fertige Stempelkarte raus. Die Antwort enthält den Google-Wallet- saveUrl — als QR-Code unten auf den Kassenbon drucken oder aufs Kundendisplay legen; der Kunde fügt die Karte mit einem Scan hinzu. Optional wird der aktuelle Bon direkt mitgebucht (gleiche Felder wie /transaction). Idempotent: existiert im Laden bereits eine Karte zu dieser Nummer/E-Mail, kommt dieselbe Karte mit frischem Save-Link zurück (created: false).
{
"customerPhone": "0151 23456789",
"displayName": "Anna",
"amountCents": 1250,
"externalRef": "BON-2026-000483"
}{
"created": true,
"passId": "p_abc123",
"serialNumber": "MW-1234-5678",
"saveUrl": "https://pay.google.com/gp/v/save/eyJhbGciOi…",
"googleLive": true,
"transaction": { "stampsAdded": 2, "newBalance": 2, "duplicate": false }
}/v1/pos/transactionStempel aus einem Kassenbon gutschreiben
Rechnet den Bon-Betrag nach der im Dashboard konfigurierten Regel in Stempel um (klassisch 1 Stempel/Bon oder betragsabhängig, mit Deckel) und schreibt sie gut. Die Wallet-Karte des Kunden aktualisiert sich automatisch per Push. externalRef (Bon-Nr.) immer mitschicken: macht die Buchung idempotent und ist Voraussetzung für ein späteres Storno. Die Bon-Positionen (items, optional) ebenfalls immer mitschicken: hat der Händler Warengruppen-Regeln aktiviert (z. B. „nur Kaffee zählt", „Pfand zählt nicht"), rechnet die API nur die Summe der passenden Positionen in Stempel um — ohne items zählt der volle Betrag. Bei aktiver Produkt-Stempel-Regel (v1.5) gilt: Stempel = Anzahl der passenden Positionen — die optionale quantity je Position zählt mehrfach (2× Cappuccino = 2 Stempel); ohne items gibt es dann 1 Stempel je Bon. Mit redeemIfAvailable: true (v1.6) wird eine voll werdende Karte direkt in derselben Buchung eingelöst — die Antwort trägt dann autoRedeem. Statt passId geht seit v1.4 auch customerPhone oder customerEmail — für Kassen ohne Scanner. Optional kann die Kasse eine Kassierer-Kennung cashierRef (v1.7) mitschicken — sie fließt in das Missbrauchs-Radar des Händlers und in den Transaktions-Audit, hat aber keine Wirkung auf die Buchung.
{
"passId": "MW-1234-5678",
"amountCents": 2350,
"externalRef": "BON-2026-000482",
"items": [
{ "name": "Cappuccino groß", "totalCents": 840, "quantity": 2 },
{ "name": "Pfand Mehrwegbecher", "totalCents": 100 }
]
}{
"passId": "p_xyz789",
"newBalance": 9,
"stampsAdded": 4,
"pointsRequiredForReward": 10,
"stampsToReward": 1,
"rewardAvailable": false,
"rewardTriggered": false,
"duplicate": false
}/v1/pos/transactions/batchOffline-/Batch-Nachlieferung (bis 100 Buchungen)
Liefert nach einem Verbindungsabbruch mehrere Bons in einem Aufruf nach. Jeder Eintrag entspricht einer transaction-Buchung, optional mit occurredAt (Original- Verkaufszeitpunkt, max. 30 Tage alt) — Abgleich und Tages-Statistik zeigen dann den echten Zeitpunkt. Fehler einzelner Einträge brechen den Batch nicht ab, sondern stehen als Per-Item-Fehler im Ergebnis. Mit externalRef je Eintrag ist der komplette Batch beliebig wiederholbar.
{
"transactions": [
{
"passId": "MW-1234-5678",
"amountCents": 2350,
"externalRef": "BON-2026-000482",
"occurredAt": "2026-07-05T18:42:00+02:00"
},
{
"passId": "MW-1234-5678",
"amountCents": 990,
"externalRef": "BON-2026-000483"
}
]
}{
"results": [
{ "index": 0, "ok": true, "transaction": { "newBalance": 9, "stampsAdded": 4, "duplicate": false } },
{ "index": 1, "ok": false, "error": { "status": 404, "message": "Pass in diesem Laden nicht gefunden" } }
],
"succeeded": 1,
"failed": 1
}/v1/pos/summaryTagesabschluss (Z-Bericht) als fertige Aggregat-Zeile
Aggregiert alle Kassen-Connect-Bewegungen eines Berlin-Kalendertags — Bons, Brutto-/ Netto-Umsatz (nach Storno), Ø-Bon, Stempel, Einlösungen, Kunden. Query-Parameter day (YYYY-MM-DD, Default heute). Erspart deiner Kasse das Selbst-Aggregieren der transactions-Liste.
{
"day": "2026-07-05",
"sandbox": false,
"receipts": 42,
"revenueCents": 61250,
"netRevenueCents": 58900,
"voidedReceipts": 1,
"avgReceiptCents": 1458,
"stampsIssued": 118,
"stampsReversed": 4,
"rewardsRedeemed": 7,
"customers": 31
}/v1/pos/redeemBelohnung an der Kasse einlösen
Zieht die volle Belohnungs-Schwelle vom Pass ab und liefert die Beschreibung der eingelösten Belohnung fürs Kassendisplay oder den Bon. Vorher per Karten-Abfrage prüfen, ob rewardAvailable ist — bei zu wenig Stempeln antwortet die API mit 400, ohne etwas zu buchen.
{
"passId": "MW-1234-5678",
"externalRef": "BON-2026-000483"
}{
"passId": "p_xyz789",
"serialNumber": "MW-1234-5678",
"redeemed": true,
"rewardDescription": "1 Döner gratis",
"newBalance": 0,
"pointsRequiredForReward": 10,
"duplicate": false
}/v1/pos/voidStorno einer Stempel-Buchung
Bucht die Stempel einer früheren Buchung zurück — referenziert über deren externalRef. Der Betrag wird auf den aktuellen Saldo geklemmt: hat der Kunde zwischenzeitlich eingelöst, rutscht er nie unter 0. Idempotent — ein zweites Storno derselben Referenz bewegt nichts mehr. Teilstorno (v1.7): mit amountCents (erstatteter Betrag) wird anteilig zurückgenommen — der behaltene Betrag „verdient" floor-anteilig Stempel, mehrere Teilstornos kumulieren. Bei Teilstornos refundRef je Erstattung mitschicken (Idempotenz bei Retries); ohne refundRef dedupliziert der Server über den Betrag.
{
"externalRef": "BON-2026-000482",
"reason": "1 Getränk zurückgegeben",
"amountCents": 450,
"refundRef": "REF-2026-000107"
}{
"passId": "p_xyz789",
"voided": true,
"originalStamps": 4,
"stampsReversed": 1,
"newBalance": 8,
"duplicate": false,
"partial": true
}/v1/pos/transactionsAbgleich-Liste (Tagesabschluss / Z-Bericht)
Alle über Kassen-Connect entstandenen Bewegungen des Ladens (Stempel, Einlösungen, Stornos), neueste zuerst, cursor-paginiert. Query-Parameter: since, until (ISO 8601), limit (max. 500), cursor.
{
"items": [
{
"id": "t_001",
"passId": "p_xyz789",
"type": "EARN",
"kind": "earn",
"pointsDelta": 4,
"balanceAfter": 9,
"externalRef": "BON-2026-000482",
"amountCents": 2350,
"createdAt": "2026-07-02T13:58:00.000Z"
}
],
"nextCursor": null
}Webhooks: Midas ruft deine Kasse
Optional pusht Midas Ereignisse an eine URL, die der Ladenbesitzer im Dashboard unter Kassen-Connect → Webhook hinterlegt — z. B. damit das Kassendisplay von sich aus „Belohnung verfügbar!“ anzeigen kann:
reward_available— eine Karte hat soeben die Belohnungs-Schwelle erreicht (egal ob per Kasse, Staff-Scan oder Bonus).reward_redeemed— eine Belohnung wurde eingelöst.pass_created— ein Kunde hat eine neue Karte erstellt.
Jede Zustellung ist HMAC-signiert (Header X-Midas-Signature, Secret whsec_… aus dem Dashboard). Antworte mit einer 2xx; sonst folgt genau ein Wiederholversuch nach 30 Sekunden — dedupe deshalb über die Event-ID. Zustellungen sind best-effort und blockieren nie eine Buchung; die API bleibt die verbindliche Wahrheit.
{
"id": "evt_5f0c2e7a-…",
"type": "reward_available",
"createdAt": "2026-07-06T14:07:31.000Z",
"sandbox": false,
"merchantId": "m_abc123",
"data": {
"passId": "p_xyz789",
"serialNumber": "MW-1234-5678",
"customerName": "Max M.",
"balance": 10,
"threshold": 10,
"stampsToReward": 0,
"rewardDescription": "1 Döner gratis"
}
}// Express-Beispiel: Signatur prüfen (Raw-Body nötig!)
const crypto = require('node:crypto');
const SECRET = process.env.MIDAS_WEBHOOK_SECRET; // whsec_… aus dem Dashboard
app.post('/midas-hook', express.raw({ type: 'application/json' }), (req, res) => {
const header = req.header('X-Midas-Signature') ?? ''; // t=<unix>,v1=<hex>
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header);
if (!m) return res.sendStatus(400);
const [, t, v1] = m;
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return res.sendStatus(400); // Replay
const expected = crypto.createHmac('sha256', SECRET)
.update(`${t}.${req.body}`).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(v1, 'hex'))) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body); // { id, type, sandbox, data: { serialNumber, … } }
// event.id dedupen — bei Zustell-Retry kommt dieselbe id erneut.
res.sendStatus(200);
});Idempotenz, Retries & Storno
Schicke bei jeder Buchung die Bon-Nr. als externalRef mit. Sie ist der Idempotenz-Schlüssel: derselbe externalRef bucht nie doppelt — Netzwerk-Timeouts kannst (und sollst) du also gefahrlos mit einem identischen Retry beantworten. Die Antwort auf einen Retry trägt "duplicate": true und das Ursprungs-Ergebnis.
externalRef ist außerdem der Anker fürs Storno: POST /v1/pos/void bucht die Stempel des referenzierten Bons zurück. Die Rückbuchung wird auf den aktuellen Saldo geklemmt — ein Kunde, der zwischen Kauf und Storno bereits eingelöst hat, rutscht nie ins Minus. Buchungen ohne externalRef sind nicht stornierbar.
Stempeln (transaction) und Einlösen (redeem) nutzen getrennte Idempotenz-Räume — dieselbe Bon-Nr. darf also in einem Bezahlvorgang sowohl eine Einlösung als auch neue Stempel tragen.
Fehlercodes
| Status | Bedeutung | Empfohlene Reaktion |
|---|---|---|
| 400 | Validierungsfehler (Body/Query) oder nicht genug Stempel beim Einlösen | Request korrigieren; bei redeem vorher rewardAvailable prüfen |
| 401 | POS-Token fehlt, ist ungültig oder widerrufen | Token im Dashboard prüfen/neu erstellen; Header-Format kontrollieren |
| 403 | Kassen-Connect nicht aktiviert ODER merchantId passt nicht zum Token | Händler aktiviert das Feature im Dashboard unter „Kassen-Connect“ |
| 404 | Pass bzw. externalRef in diesem Laden nicht gefunden | Seriennummer prüfen — Pässe anderer Läden sind bewusst unsichtbar |
| 5xx | Serverfehler | Mit gleichem externalRef erneut senden — Retries buchen nie doppelt |
Fehler-Antworten folgen dem üblichen Schema { "statusCode": 403, "message": "…" }.
Code-Beispiele
const BASE = 'https://midaswallet.de/api';
const TOKEN = process.env.MIDAS_POS_TOKEN; // mw_pos_…
async function stampReceipt(passId, amountCents, receiptNo) {
const res = await fetch(`${BASE}/v1/pos/transaction`, {
method: 'POST',
headers: {
Authorization: `Bearer ${TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ passId, amountCents, externalRef: receiptNo }),
});
if (!res.ok) throw new Error(`Midas Wallet: HTTP ${res.status}`);
return res.json(); // { newBalance, stampsAdded, rewardAvailable, … }
}<?php
function midasStampReceipt(string $passId, int $amountCents, string $receiptNo): array {
$ch = curl_init('https://midaswallet.de/api/v1/pos/transaction');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('MIDAS_POS_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'passId' => $passId,
'amountCents' => $amountCents,
'externalRef' => $receiptNo, // Bon-Nr. => idempotent + stornierbar
]),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status !== 200) throw new RuntimeException("Midas Wallet: HTTP $status");
return json_decode($body, true);
}Du baust eine Kassen-Integration?
Wir unterstützen Kassenhersteller und Agenturen gerne direkt — mit Test-Zugängen, Review der Anbindung und einem kurzen Draht bei Fragen.