Connecting your platform to a payment gateway or payment service provider looks simple in the documentation: send a request, receive a response, show a result. In production it behaves very differently. Networks time out, providers send the same callback twice, statuses change after the fact and finance wants every cent explained. This article collects the pitfalls we see most often when building deposit and payment flows for trading and eCommerce platforms, and the practices that avoid them.
1. Treating the HTTP response as the truth
A timeout does not mean the payment failed. The provider may have processed it and the response was lost. If your code marks the order as failed and lets the customer retry, you risk a double charge. Treat the outcome as unknown until you confirm it by querying the provider or receiving a webhook, and model "pending" as a real state.
2. No idempotency
When you retry a request you must be sure it will not be applied twice. Most providers support an idempotency key. Generate a stable key per payment attempt, persist it before sending, and reuse it on retries. On your side, make the endpoints that receive callbacks idempotent as well, keyed by the provider's transaction identifier.
3. Trusting webhooks without verification
Anyone who finds your webhook URL can post to it. Verify signatures or shared secrets, check the source where the provider supports it, and never mark a payment as successful from the payload alone. A safer pattern is to treat the webhook as a signal to fetch the authoritative status from the provider's API.
4. Processing webhooks synchronously
If your handler does heavy work before returning, the provider will time out and retry, creating duplicates and load. Accept the webhook, validate it, write it to a queue or table, return success quickly, and process it asynchronously with retry and dead-letter handling. A message broker such as RabbitMQ is a natural fit for this.
5. Out-of-order and duplicate events
Events can arrive in a different order than they happened, and the same event can arrive several times. Use state machines with allowed transitions, ignore events that would move a payment backwards, and store the provider's event identifier to detect duplicates.
- Define states: created, pending, authorised, captured, failed, refunded, chargeback.
- Define allowed transitions, and log rejected ones for review.
- Keep every event in an append-only table for auditing.
6. Floating point money
Never store amounts as floating point numbers. Use integer minor units or a decimal type, store the currency alongside every amount, and be explicit about rounding rules. Currency conversion and fees should be recorded as separate entries rather than overwritten values.
7. Missing reconciliation
Even a perfect integration drifts from reality over time. Reconcile your records against the provider's settlement or statement reports on a schedule, flag mismatches, and give finance a screen to resolve them. This is the control that catches the missed webhook, the manual adjustment made in the provider portal and the fee you did not model.
8. Ignoring provider differences
If you integrate more than one provider, resist exposing their quirks to the rest of your system. Define your own internal payment model and statuses, and write an adapter per provider that maps to it. Adding the next provider then means writing one adapter, not changing the application. This approach is central to the multi-PSP work in our CPT Markets platform.
9. Weak error handling and unclear user messages
Providers return many error codes. Map them to a small set of categories: retryable, customer-fixable, declined and fatal. Show customers a helpful message without leaking internal detail, and log the raw response with a correlation identifier for support.
10. Skipping test and sandbox discipline
Sandboxes rarely match production perfectly. Maintain automated tests that simulate timeouts, duplicate webhooks, partial refunds and declined cards. Run a small live transaction with a real card after every provider change, and keep feature flags so you can disable a provider quickly if it misbehaves.
Security and compliance basics
Keep card data off your servers by using hosted fields or redirects. Store secrets in a vault, rotate them, restrict outgoing traffic to known provider hosts where you can, and log access to payment records. If your business is regulated, make sure retention, audit and reporting requirements are part of the design from the beginning rather than bolted on.
A checklist you can use
- Every outbound payment request has an idempotency key.
- Every inbound callback is verified, deduplicated and queued.
- Payments follow a state machine with an append-only event history.
- Amounts use decimal or integer minor units with currency.
- Daily reconciliation runs and unmatched items are visible to finance.
- Providers sit behind an internal adapter interface.
- Failure and timeout scenarios have automated tests.
Designing the payment state machine in practice
A payment is not a boolean. It is a small workflow with states and legal transitions, and writing it down is the single most useful design step. Draw the diagram, including the unhappy paths: authorised but never captured, captured then partially refunded, refunded then charged back, pending that never resolves. For each transition decide what triggers it, what it records and what it notifies.
Implement transitions in one place and make every other part of the system call it, so there is exactly one piece of code that is allowed to change a payment's status. Record the previous state, the new state, the trigger and the raw provider payload in an append-only history. When finance asks why a customer was credited twice, you will be able to answer with evidence in minutes.
Timeouts, retries and backoff
Choose timeouts deliberately. Too short and you create unknown outcomes for payments that were actually accepted; too long and you tie up threads and frustrate customers. Retry only operations that are safe, such as idempotent requests and status queries, with exponential backoff and a cap. For operations that move money, prefer to fall back to a status check over a blind retry.
Operational visibility
- Dashboards for success rate, decline reasons and latency per provider.
- Alerts when the pending count or webhook failure rate rises.
- A support tool that shows a payment's full history by reference without database access.
- A runbook for switching traffic away from a failing provider.
Payments are a part of the business where small mistakes become money lost or customers annoyed, so the tooling around the integration deserves as much attention as the integration itself.
Need help?
Payment integrations are a core part of our API development and system integration and FinTech platform work. If you are connecting a new provider or untangling an existing integration, we can review the design and the failure modes before they cost you money.