Skip to main content
Vibepay has no plastic card. The cardholder’s card is a pass in Apple Wallet or Google Wallet, and the pass shows a QR code. That code is the payment credential.

What you scan

A string beginning with vqr_:
Inside it is a cryptographically signed reference to a card. Your POS does not need to know any of that — pass the scanned string through byte for byte as the qrToken field.
Do not parse it, do not split on the ., do not strip the vqr_ prefix, do not uppercase it, and do not re-encode it. The signature covers the exact bytes; any modification makes it invalid.

It is single-use

This is the most important property of the system, and the one most likely to surprise you. The moment a charge against a code commits, that code is retired — in the same instant, as part of the same database write. The pass immediately begins showing a new one. So a 409 qr token already used does not mean something went wrong. It usually means the payment already succeeded and you are looking at a duplicate scan or a retry. Confirm with GET /v1/transactions before you either hand over goods or charge again. This is also why you must never cache a scanned code, log it for later replay, or let a cashier re-submit an old one from a screen history. It will not work, and the attempt is indistinguishable from fraud.

It also expires

A code stops being accepted 24 hours after the pass created it, whether or not it was ever charged. That is a backstop against an old screenshot, not the main defence — the single-use rule is. In practice a pass refreshes its code well within that window, so an expired code almost always means the customer’s phone has been offline for a long time. The fix is the same either way:
TOKEN_EXPIRED → ask the customer to open their Vibepay pass and show it again. Opening the pass refreshes the code. Then scan and charge afresh — with a new idempotency key, since this is a new attempt at the same sale only if you kept the original key. See Idempotency.

Scanner requirements

If your scanner appends a carriage return or a configured prefix, strip it in your integration before sending — an extra byte is a TOKEN_INVALID.

Errors you will see

Now send the charge

Request fields, amount rules, and the response.