🔁 Handling Failures and Recovering Transactions

Retry requests safely with idempotency keys and recover operations whose outcome you don't know, without duplicates or data loss.

A network error, a timeout or a crash on your side can leave you without a response to a request that moves money. You then don't know whether Infinia created the operation.

This page explains how to retry safely with idempotency keys, and how to use list endpoints to find out what happened to an operation and bring your records in sync.

Idempotency keys

An idempotency key is an identifier of your own that you send when you create an operation. The field name depends on the resource:

ResourceField in the request body
PayoutoriginId (required)
Internal transferidempotency_key
Deposit refundidempotency_key
Payin refundidempotency_key

If you send a key that your company already used for that resource, the API returns the operation that the first request created instead of creating a second one. This makes it safe to retry after a timeout or a 5xx error.

Payins don't have an idempotency key. Sending the same payin request twice creates two payins. Send your own reference with each payin, and before you retry a payin, look it up by reference as described in Recover an operation.

To use idempotency keys:

  1. Generate a unique key for the operation, for example a UUID, and save it in your database before you send the request.
  2. Send it in the idempotency field of the request body.
  3. If the request fails with a network error or a 5xx, retry with the same key and exponential backoff.
  4. Don't retry 4xx errors. They mean the request itself has a problem, and retrying with the same key returns the same error.
⚠️

Save the key before you send the request, not after you receive the response. If your process crashes between sending the request and saving the key, you have no key to recover the operation with.

Recover an operation

If retries run out and you still don't know the outcome, list the resource filtered by your key. List endpoints return the current state of each operation, and they're the authoritative source for it.

Paths are relative to https://app2test.infiniaweb.com/infinia_api in sandbox.

ResourceRequest
PayoutGET /v2/payouts/?origin_id={your_key}
PayinGET /v1/payments/?reference={your_reference}
Internal transferGET /v1/accounts/internal-transfer/?idempotency_key={your_key}
Deposit refundGET /v1/accounts/movements/refund/?idempotency_key={your_key}
Payin refundGET /v1/payments/refund/?idempotency_key={your_key}
  • The response contains a record. The operation was created. The record holds its current status, so update your local record to match it.
  • The response is empty. The request never reached Infinia. It's safe to retry or reschedule it.

A retry that arrives while Infinia is still processing the first request with the same key can fail with a 4xx, for example a 400 for payouts or a 409 for internal transfers. When a retry fails with a 4xx, look the operation up by your key before you treat it as failed.

Request lifecycle

Follow this flow for every operation you create:

  1. Generate your idempotency key and save it with your local record, marked as pending.
  2. Send the POST request.
    • 2xx: Save the returned ID and status on your local record.
    • 5xx or timeout: Retry with exponential backoff and the same key. When retries run out, go to step 3.
    • 4xx: Don't retry. Read the error and fix the request.
  3. If the outcome is still unknown, list the resource by your key and update your local record.
  4. Wait for the final status in a webhook.

Reconcile pending operations

Network errors and crashes can leave a local record in a non-final status longer than expected. A periodic reconciliation job catches these cases:

  1. Find the records in your database that are still in a non-final status after your expected processing window, for example 30 minutes.
  2. For each one, list the resource filtered by its idempotency key.
  3. If a record comes back, update your local status to match it.
  4. If nothing comes back, the request never reached Infinia. Decide whether to retry it or mark it as failed, based on your business rules.

Use webhooks with the list endpoints as a fallback

Webhooks deliver status changes in near real time and are the right way to react to them. A webhook can still be missed, for example when your endpoint is unreachable for longer than the retry window or your process restarts. In both cases, use the list endpoints to fill the gap.

You don't need Infinia's operation ID to match a webhook to your records. The body includes the key you sent (originId, reference or idempotency_key, depending on the resource), so you can look the record up by your own identifier.

📘

See Webhooks for signature verification, deduplication with the X-Idempotency-Key header, and the retry schedule.

Summary

SituationWhat to do
The request timed outRetry with the same key and exponential backoff.
You received a 5xxRetry with the same key and exponential backoff.
You received a 4xxDon't retry. Fix the request. If it was a retry, look the operation up by your key first.
Retries ran out and the outcome is unknownList the resource filtered by your key.
A local record stays pending too longReconcile it with the list endpoint.
A webhook was missed or not deliveredList the resource and update your records.