How to handle network errors, timeouts, and unknown transaction states without duplicates or data loss.
This page explains how to use idempotency keys and List endpoints together to handle retries and recover operations whose outcome is uncertain.
Idempotency keys
Every POST endpoint accepts a unique identifier in the request body. The field name varies by resource:
| Resource | Field name |
|---|---|
| Payout | originId |
| Payin | reference |
| Internal Transfer | idempotency_key |
If you submit two requests with the same key, the API returns the result of the first request instead of executing the operation again. This makes it safe to retry after a timeout or a 5xx error without risking a duplicate transaction.
The recommended pattern:
- Generate a unique identifier for the operation (e.g. a UUID) and persist it in your database before sending the request
- Include it as the idempotency key field in the request body
- If the request fails due to a network error or
5xx, retry with the same identifier and exponential backoff - Do not retry on
4xxerrors — those indicate a problem with the request itself, and the same key will return the same error
Persist the key before you send, not after you receive a response. If your process crashes between sending the request and saving the key, you lose the ability to recover the operation.
Recovering an operation
After exhausting retries, if the outcome of an operation is still uncertain, query the List endpoint filtered by your idempotency key. List endpoints always return up-to-date results and are the authoritative source for the current state of any resource.
| Resource | How to query by key |
|---|---|
| Payout | GET /v2/payouts/?origin_id={your_key} |
| Payin | GET /v1/payments/?reference={your_key} |
| Internal Transfer | GET /v1/internal-transfers/?idempotency_key={your_key} |
If the response contains a record, the operation executed — the record includes the current status, so you can retrieve the final outcome directly from the response and sync your local state accordingly. If the response is empty, the operation never reached Infinia and it is safe to retry or reschedule.
Request lifecycle
Every outgoing operation should follow this flow:
- Generate and persist your idempotency key locally (mark the record as
PENDING) - Send the
POSTrequest2xx→ update your local record with the returned ID and status5xxor timeout → retry with exponential backoff using the same key; after max retries, query the List endpoint (step 3)4xx→ do not retry; inspect the error and fix the request
- If the outcome is still unknown, query the List endpoint by your key and sync local state
- Await the final status update via webhook
Reconciliation
Network errors and process crashes can occasionally leave records in a non-final state longer than expected. A periodic reconciliation job catches these cases by scanning for operations that have been PENDING beyond a reasonable threshold and querying Infinia to sync them.
Recommended approach:
- Query your database for records in a non-final state older than your expected processing window (e.g. 30 minutes)
- For each record, call the List endpoint filtered by its idempotency key
- If a record is found, update your local state to match its current status
- If no record is found, the operation never reached Infinia — decide whether to retry or mark it as permanently failed based on your business rules
Webhooks
Webhooks deliver state changes in near real-time and are the right way to react to events as they happen. Webhook delivery can fail if your endpoint is temporarily unreachable, or you may miss events after a process restart. In both cases, the List endpoint is your fallback — use it to reconcile any gaps.
When processing a webhook, you don't need to use Infinia's resource ID to match it to your internal records. The payload includes the idempotency key you originally submitted (originId, reference, or idempotency_key depending on the resource), so you can look up the corresponding record using your own identifier.
See the Webhooks page for payload verification, deduplication using the
X-Idempotency-Keyheader, and the automatic retry schedule.
Summary
| Situation | Action |
|---|---|
| Request timed out | Retry with the same key and exponential backoff |
Received 5xx | Retry with exponential backoff, same key |
Received 4xx | Do not retry — fix the request |
| Exhausted retries, outcome unknown | Query the List endpoint filtered by your key |
Record stuck in PENDING | Reconciliation using the List endpoint |
| Webhook missed or undelivered | Query the List endpoint |

