Blog · 2026-09-21
The one header that decides whether your POS integration is trustworthy
A guest buys a week of WiFi. The cashier taps the button. The network hiccups, the request takes eleven seconds, the till gives up and shows an error. The cashier — reasonably — taps it again.
Did that guest just get one voucher or two?
This is not an edge case
It is a Tuesday. Tills sit on the worst network in the building, behind the same WAN link everything else is fighting over. Timeouts are normal. The question is not whether the retry happens, it is what your API does when it does.
Two vouchers for one payment is worse than it sounds. The guest has a spare code they did not pay for. Your voucher count and your takings disagree. Nobody notices for a month, and when they do, the first thing anyone suspects is staff.
The header
Every write in the GuestPass API requires an Idempotency-Key. Use the POS
order id — it is unique per sale and you already have it.
POST /api/v1/sites/{site}/vouchers
Idempotency-Key: libpay-order-84213
Send it twice with the same body and the second call replays the first response, byte for
byte, with Idempotent-Replay: true. No second voucher. Retry as often as you
like.
The part that is easy to get wrong
Reuse the same key with a different body and we return 409 rather
than the stored response.
That looks unhelpful until you think about what it means. A caller sending the same key for two different sales has a bug — probably a key derived from something that is not unique per transaction, like the till id or the date. Quietly handing back the old voucher would hide that bug for months and give the second guest someone else's code. Failing loudly at the moment the bug appears is the kinder option.
Retry the 502s
If the on-site controller is unreachable you get 502 controller_unavailable.
That one genuinely is retryable — same key, try again. Do not show a cashier a raw error;
show "try again", and log it so somebody notices the bridge is down.