Skip to main content
The dangerous moment in any payment integration is a request you sent but never got an answer to. The charge may have committed. Retrying blindly charges the customer twice; giving up may hand over goods that were never paid for. An idempotency key removes the guess.
Send the same key on every retry of the same sale, and Vibepay collapses them into exactly one payment. The first request charges; every repeat gets the original transaction back.

The rule that matters

One key per sale attempt — reused across every retry of that attempt. A key generated fresh on each HTTP call protects nothing: the retry carries a different key and becomes a second, separate charge.
Generate the key when the cashier confirms the amount, hold it for the whole attempt, and throw it away only when you get a final answer.

What a repeat returns

201 — same transaction

The repeat matched the original in every respect. You get the original transaction back, with the same id. The customer was charged once.

409 — conflict

The key was reused for a different charge, or the original has since been reversed. Nothing was charged. Use a fresh key for a genuinely new sale.
Because a matched replay returns 201, your success path needs no special handling at all — the retry simply succeeds. That is the point.

Choosing keys

Since keys are scoped to your merchant rather than to a terminal, a bare sequential counter per till will eventually collide with the till next to it. Prefix it, or use a UUID.

Never send a blank key

400 Bad Request
An empty header is rejected on purpose. It reads, in code and in logs, as though idempotency is implemented — while doing nothing at all. That is precisely the failure that double-charges a customer on a timeout, so the API refuses it at the boundary instead of letting it through. If you genuinely do not want retry collapsing, omit the header. That is allowed, and it is explicit.

What is protecting the customer

Three independent mechanisms, so a gap in one is covered by another:
1

The idempotency key

Collapses your retries into one transaction.
2

The single-use QR code

Even with no key at all, a scanned code cannot be charged twice — the second attempt is a 409.
3

Replay resolution

A retry that arrives after the original committed is recognised as a repeat rather than reported as a spurious decline, even though the balance and the code have both moved on.
You should still send a key. The QR code protects against charging the same scan twice; only the key protects against a retry of a request whose outcome you never learned.