How your ATM redeems a CoinTrak pulse machine's cash-out for a player — scan the slip, confirm the amount with us, get authorization, dispense, and report it paid.
A CoinTrak pulse gaming machine prints a redemption slip when a player cashes out. The player brings it to your ATM Money-Kiosk, which scans the slip's QR, asks CoinTrak what it's worth, gets authorization, dispenses the cash, and tells CoinTrak it paid.
Your ATM talks to a small local HTTPS service on the CoinTrak device on the same venue LAN — there is no internet round-trip at pay time.
https://<COINTRAK-LAN-IP>:<PORT> — we provide the IP/port per install (TBD).In this exact order. The authorize ACK is your go-signal and the paying step — the slip is marked paid when we ACK, so a jam, a crash, or a lost confirm all leave the slip paid and send the player to a clerk. This deliberately favors "paid" so the ATM can never pay the same slip twice.
https://<host>/receipt/hand/<transaction_id> — take the last path segment as transaction_id.GET /atm/handpay/lookup → amount + status.POST /atm/handpay/authorize → we validate and mark the slip paid, then reply authorized:true.authorized:truePOST /atm/handpay/confirm — report the outcome (the slip is already paid).We log the dispense for reconciliation. Send dispensed_cents + dispense_ref.
The slip stays paid — send the player to a clerk, who verifies and pays by hand.
The slip QR encodes a full URL: https://<host>/receipt/hand/<transaction_id>.
Read the complete URL and take the last path segment as the
transaction_id — that's the key for all three calls. The QR content may change in a
later revision; we'll tell you if so.
All requests/responses are JSON over mTLS. All money is integer cents.
Returns the single handpay for that tx (never lists others).
200 { "transaction_id":"…", "amount_cents":2500, "status":"pending",
"machine_name":"…", "claimable":true }
404 { "error":"not found" }
409 { "error":"not-claimable", "status":"paid|voided|held" }
Validates the slip is pending and the amount matches, then marks it paid
and ACKs. Include a stable atm_id. Dispense only on authorized:true.
// request
{ "transaction_id":"…", "amount_cents":2500, "atm_id":"ATM123" }
200 (ACK — slip is now PAID) { "authorized":true, "transaction_id":"…", "amount_cents":2500 }
409 { "authorized":false, "error":"already-paid|voided|not-found|amount-mismatch" }
Idempotent: re-sending authorize for a slip your ATM just paid
re-returns the ACK (a lost ACK still lets you dispense). A slip already paid by someone else, or
voided/unknown → 409, do not dispense.
A report only — it records the dispense for reconciliation; it does not change the paid status. Idempotent — if you don't get a 200, retry the same request (the slip is already paid, so repeats are safe).
// success → record the actual cash out
{ "transaction_id":"…", "result":"success", "dispensed_cents":2500, "dispense_ref":"ATM123-000457" }
200 { "ok":true, "status":"paid", "logged":true }
// fail / short dispense → slip STAYS PAID; player goes to a clerk
{ "transaction_id":"…", "result":"fail", "reason":"jam" }
200 { "ok":true, "status":"paid", "clerk_review":true }
dispense_ref is your ATM's own unique reference for the dispense (required on success —
we reconcile against your records). On fail the player is directed to an attendant; the
slip is not reopened for another automated payout.
A periodic meter dump from the ATM — this is reconciliation leg 3 (your dispense records, tied out against the machine meters and our ledger). We're proposing the endpoint; you tell us the payload. A sketch of what's useful:
{ "atm_id":"ATM123", "ts":"2026-09-10T18:00:00Z",
"cash_dispensed_cents_total":1234500, // lifetime cumulative
"dispense_count_total":418,
"cassettes":[ { "denom_cents":2000, "count":300 } ], // optional levels
"last_dispense_ref":"ATM123-000457", "firmware":"…" }
200 { "ok":true }
Send us what your ATM actually exposes and your preferred cadence (e.g. every few minutes and/or after each dispense) — we'll finalize the schema around your fields.
CoinTrak issues and provides your certificate per install — you don't generate anything. We send you a bundle containing:
We deliver it over a secure channel (e.g. a password-protected PKCS#12 / .p12). Keep
the private key protected on the ATM and validate the CoinTrak server cert against the provided CA. Certs
are per-site with a fixed validity; we coordinate rotation on reinstall.
authorize → authorized:true (the slip is paid as of that ACK).authorize returns 409, do not dispense — the slip is already paid, voided, or unknown.confirm(success) until you get a
200 — safe (idempotent); it closes our dispense log.confirm(fail) and direct the player to an attendant/clerk — the slip stays paid; don't treat it as unpaid or retry another automated payout.If your ATM dispensed but the confirm never reached us (crash/network), the slip is
already paid, so nothing double-pays — just retry confirm to close the
dispense log. A paid slip with no dispense record is what routes a genuine non-dispense to a clerk,
reconciled against your log via dispense_ref.
To finalize this contract:
confirm(success), can you return dispensed_cents + dispense_ref? (Required for reconciliation.)amount_cents (all-or-nothing), or can you do a partial dispense? Either way the slip is paid in full at authorize; a short/failed dispense sends the player to a clerk for the shortfall.atm_idauthorize, so a retried authorize after a lost ACK re-ACKs instead of failing.POST /atm/meters)