Error Handling & Timeouts
This guide covers how payment failures and timeouts surface across your frontend and backend, which errors are worth retrying, and how to size your payment session timeouts.
Where results arrive
Payment outcomes reach your systems through two channels, and they behave differently in failure scenarios:
| Scenario | Webhooks to your backend | Frontend signal |
|---|---|---|
| Payment successful | created, then completed | Success event / redirect with status=completed |
| Payment failed at processing | created, then a failure status (not_completed, not_authorised, cancelled, …) | Failure event / redirect with the failure status |
| Payment timed out | created only — a failure webhook may not arrive | Timeout event after the timeout period elapses |
Two consequences for your design:
- A
createdwebhook with no follow-up does not mean failure — the customer may still be mid-payment. Treat the charge as pending until you receive a terminal status or your payment timeout elapses. - Timeouts are primarily a frontend signal. Your backend should run its own timeout clock from order creation rather than waiting for a webhook that may never come. The payment UI prevents the customer from completing a payment after the timeout period has elapsed.
Retryable and non-retryable errors
From the customer's perspective, assume all payment errors are retryable unless told otherwise: let the customer try again with the same or a different payment method. DataMesh signals non-retryable errors explicitly — via a frontend event (including window timeout or closure) and, for processing failures, a webhook with the failure status.
When a charge fails, use the charge's root-level error_message for customer display and log psp_responses[].psp_error for diagnostics — see the Charge Data Model. The Error Messages reference lists every error type with response code, resulting charge status, and whether it can be retried.
A failed charge never leaves the customer charged — see the status reference.
Sizing your payment timeout
Most payments complete within 15–30 minutes of order creation. Past the one-hour mark you can safely assume the payment will not complete (late completions are a rare edge case — if you void an order and a completed webhook arrives later, you will need to reconcile it manually or refund).
Recommendations:
- Align your frontend and backend timeouts. Keep the payment window open until the timeout expires, and have your backend continue attempting to recover the payment result for at least one minute after the timeout — a payment initiated just before the deadline can still land.
- Hold the order while a charge is in a pending status; release it when your timeout elapses. The status reference marks which statuses to treat this way.
- Size the timeout to the slowest payment method you have enabled. Maximum payment windows differ per payment method and per country — bank transfers and some wallets allow the customer hours to pay, while others expire in minutes. The limits for your configuration are confirmed during onboarding.
Your backend is only notified that a wallet instrument was selected — not which wallet. Since different wallets allow very different payment windows (from a few minutes to as long as 72 hours, depending on the wallet and configuration), set your wallet timeout to the maximum duration across all wallet types you have enabled.
Indicative limits — Indonesia
| Payment method | Payment window |
|---|---|
| Card | 60 minutes |
| Wallet | 60 minutes typical; individual wallets range from 5 minutes to a configurable maximum of 72 hours |
| Bank transfer / virtual account, QRIS | Confirmed during onboarding |
Timeout limits are country- and configuration-specific — do not copy values between markets. Confirm the limits for your payment methods with DataMesh during onboarding.
Handling duplicate and out-of-order webhooks
Webhook delivery is retried until acknowledged, so duplicates are expected — handle them idempotently using the delivery id. See How to Accept a Webhook and the retry policy.