Skip to content
Back to Blog
Software
Updated

API integrations: retries without duplicate business actions

A timeout does not tell you whether an action happened. Give each business operation a stable identity so retries can recover without creating duplicates.

Branded abstract 5e Labs cover image for API integrations: retries without duplicate business actions

Idempotent API integrations let the same logical operation be attempted again without creating another business action. The difficult case is a timeout after the receiving system has already acted. Your application knows the response is missing; it does not know the action failed.

Solving that ambiguity requires a durable operation identity and a recovery path. Adding a retry loop alone makes the duplicate-action risk worse.

Give the action an identity, not each attempt

Create an operation key when the application accepts the business command. Reuse it for every attempt of that command, including retries after a worker restart. A fresh key on each HTTP request defeats deduplication.

Scope the key to the tenant and action type. Store a fingerprint of the intended parameters so accidental reuse with different data produces a conflict rather than an unrelated success response. Protect concurrent submissions with a database uniqueness constraint or equivalent atomic mechanism.

The provider’s contract matters. Stripe’s idempotency documentation specifies that API v1 replays stored responses, including certain errors, and describes retention limits. Check the particular API version and endpoint instead of assuming every service keeps keys forever or treats failures identically.

Make local state survive a crash

Keep an operation ledger with the key, request fingerprint, status, attempt history, and destination reference. Record acceptance durably before background dispatch. If accepting a local change must also schedule an external call, a transactional outbox can record both intentions in the same database transaction.

An outbox does not make the remote action exactly once. It makes dispatch recoverable. The receiver still needs deduplication, or the sender needs a way to reconcile the destination state before trying an ambiguous operation again.

Use explicit states such as pending, in progress, succeeded, failed, and outcome unknown. The last one deserves attention: it prevents support staff from treating every missing response as permission to create a new action.

A hypothetical shipment retry

A warehouse application asks a carrier to create a shipment. The carrier accepts it, but the response is lost. The worker retries with the same operation key. If the carrier supports the agreed idempotency contract, it can identify the original action and return the existing shipment result.

If it offers no such contract, first look up the stable external order reference. If that lookup cannot distinguish absence from delayed visibility, pause for reconciliation. A local deduplication row cannot prevent a remote service from creating two shipments after two independent requests.

SituationNext action
Invalid requestCorrect input; avoid automatic repetition
Transient failure with safe retry contractRetry with backoff, jitter, and the same key
Timeout with uncertain outcomeReconcile or retry under the documented contract
Existing key with different parametersReject the conflict

Test the missing response, not just the happy path

Simulate a lost response after success, simultaneous submissions, a worker crash, and a retry outside the provider’s retention window. Confirm the business record count, not merely the HTTP status. Apply the same thinking to incoming webhook event processing.

For the next integration review, ask the team to demonstrate recovery from one deliberately lost response. That exercise makes a concrete acceptance criterion for custom software development and exposes assumptions that a successful demo never tests.

Have a project in mind?

Get in Touch