Critical warnings
These are the failure modes that cause financial incidents in new integrations. Each one is short, specific, and has cost somebody money.
1. Generating a new externalId on retry
What breaks: a network timeout becomes a duplicate payment.
The externalId is the idempotency key. Reusing it on a retry lets LB Pay recognise the
repeated intent and return the original transaction. Generating a fresh UUID tells the
platform this is an unrelated operation — and it executes a second time.
➡️ Idempotency
2. Treating a timeout as a failure
What breaks: you refund a player whose withdrawal actually settled, and pay twice.
A timeout or a 503 means no answer — not no transaction. Wait, then query by
externalId, and act on what the GET returns.
3. Cancelling a withdrawal without querying it first
What breaks: you cancel a withdrawal that already settled, and pay the player twice.
A cancellation is a decision about an outcome. If you have not confirmed the outcome, you are guessing — and the API is the only thing that knows.
Never cancel a withdrawal request without first running the GET:
GET /v1/pix/withdraw/{externalId}
Act on what it returns. If it is still INITIATED, CONFIRMED or PROCESSING, the answer is
to wait, not to cancel.
4. Closing a charge on the first payment error
What breaks: you discard a deposit the player is about to pay, and the money arrives with nobody expecting it.
A failed payment attempt does not cancel the QR Code. The charge stays valid until it is paid or expires, and the player can simply try again.
What you receive is an error webhook with no final status — pixChargeRejected carries
"status": "pending" and a rejectedReason. It is a progress report, not an outcome.
Your system must accept that event without treating it as terminal, keep the charge open, and
keep waiting for the real ending: pixChargePaid or pixChargeExpired.
➡️ Deposits
5. Acting on a transitional status
What breaks: you cancel or reverse a withdrawal that was still in flight.
INITIATED, PENDING, PROCESSING and CONFIRMED mean the outcome is unknown. Never
return balance, cancel an operation, or block a retry while a transaction is in one of them.
6. Not deduplicating webhooks
What breaks: the player is credited twice for one deposit.
The platform retries delivery. The same pixChargePaid can arrive more than once. Enforce
uniqueness on endToEndId at the database level, not in application code.
7. expiresIn out of sync with your interface
What breaks: players who pay a valid-looking QR Code are rejected.
If the charge expires in 300 s and your countdown shows 10 minutes, payments made after
minute 5 fail. Use at least 600 s and drive the countdown from expiresAt.
➡️ Deposits
8. Retrying 422 with the same data
What breaks: support noise, and automatic compliance blocks on your account.
A 422 is a business rule the platform already evaluated. Sending the same request again
will be rejected the same way, every time.
What resolves it is different data — and what "different" means depends on the direction:
| Flow | What the player must do |
|---|---|
| Deposit | Pay again from a bank account registered under their own CPF. A retry with a compliant account succeeds. |
| Withdrawal | Supply a different destination account that belongs to them. The one they sent is not theirs. |
So do not retry automatically, and do not present it to the player as a system error. Surface it as an actionable message: the account must be in their own name.
➡️ CPF Shield
9. No contingency polling
What breaks: every event lost during a deploy stays lost.
Webhooks are not guaranteed. A sweeping job that resolves transactions stuck in a transitional state is mandatory, not optional — and it should back off as the transaction ages, so a busy minute does not turn into a poll storm.
A schedule that works:
| Age of the transaction | Interval |
|---|---|
| < 30 s | do not poll — wait for the webhook |
| 30 s – 2 min | every 30 s |
| 2 – 10 min | every 2 min, plus an internal alert past 5 min |
| 10 – 60 min | every 10 min |
| > 1 h | hourly, and escalate to a human queue |
Each step roughly quadruples the interval: the transactions that resolve normally cost you two or three queries, and the rare stuck one is still watched hours later without flooding the API.
10. Not storing the endToEndId
What breaks: you cannot refund, reconcile, or answer a support question.
➡️ Statement
11. Releasing player balance before the debit is final
What breaks: the player spends balance that is already leaving the account.
Debit the wallet before calling the withdrawal endpoint and mark it "processing". Return
it only on a confirmed FAILED.
➡️ Withdrawals