Back to Blog

Reliable API Integrations: Handle Timeouts Without Duplicate Orders

Editorial illustration of software modules joined by a bridge and a backup data path

Design shipping, payment, and service integrations around uncertain outcomes, safe retries, verified events, and recovery, using a worked shipment example.

You click Create shipment and receive no response. Perhaps the request never arrived. Perhaps the carrier created a label and the connection failed on the way back. Calling both situations a failure encourages another click, potentially producing two labels and two charges. A reliable API integration must preserve uncertainty and resolve it, not merely demonstrate that one request can succeed.

Define the business operation before choosing the endpoint

For each integration, write down the operation, required inputs, authority for the final result, recovery options, and cost of duplication. Looking up a city is different from booking a courier or opening a subscription. An endpoint URL and access key do not explain what the business should do when the answer is missing.

Consider a hypothetical bookstore creating one shipment for internal order ORD-204. The customer has approved a delivery address. Store that approved version alongside the items being shipped. Reopening the order page should not silently change the payload associated with an already submitted operation. Decide what cancellation means, too: abandoning a local draft and cancelling an accepted carrier booking are separate actions. Include these boundaries in the project brief.

Model what is known, not what the button looks like

A useful shipment workflow could distinguish ready, processing, awaiting verification, confirmed, and needs attention. Create a durable operation reference before contacting the carrier. A trusted successful response can attach its shipment ID. A timeout without a conclusive answer should move the operation into awaiting verification, not cancelled or ready to create again.

MDN's idempotency explanation distinguishes repeated requests from repeated effects; POST requests do not automatically carry that guarantee. Choose retry behavior from the provider's contract. Disabling a button temporarily improves the interface but does not protect against a browser reload, another device, or concurrent requests.

Give one logical operation one retry identity

When a provider supports an idempotency key, retain the same key for retries of the same operation with the same parameters. A new key for every click defeats the purpose. Reusing an old shipment's key for a different shipment is equally wrong. Stripe's documented implementation has specific retention and parameter-comparison rules; those rules are not universal across providers.

Suppose the carrier created SH-781 but its response disappeared. ORD-204 remains attached to the original operation. A recovery worker looks it up using the supported merchant reference or key, verifies the match, and records SH-781. If the carrier offers neither safe lookup nor deduplication, a human review can be appropriate before another write. Pausing is preferable to guessing whether a costly action happened.

Budget time and retries explicitly

  • Set a request timeout and an overall operation deadline so a customer never waits indefinitely.
  • Separate invalid input, invalid credentials, throttling, temporary interruption, and uncertain completion.
  • Where repetition is safe, use bounded retries with increasing delays and modest random variation.
  • Move exhausted operations into an actionable review queue rather than silently dropping them.

A provider may send Retry-After as a date or delay. Interpret it correctly. In JavaScript, fetch does not reject solely because of an HTTP error such as 404; inspect the response. Also, cancelling local waiting does not undo work that the remote server already performed.

Treat later events as part of the workflow

The provider might confirm an outcome through a webhook rather than the customer's return page. Stripe's event documentation covers signature verification and duplicate events; check the corresponding rules for your actual provider. In the bookstore, an older event must not move a delivered shipment back to packing. Define permitted transitions and compare the provider account, reference, and event type before applying an update.

Persist a verified incoming event durably before acknowledging receipt when processing will happen asynchronously. Maintain a record of handled events, and add reconciliation for operations waiting too long. Notifications alone are not a recovery strategy. Show staff an operation reference, latest attempt, current age, and safe next action. Do not expose credentials or unfiltered responses containing customer information.

Turn the failure story into acceptance tests

  1. The carrier accepted the request but its reply vanished: recovery finds one shipment, not two.
  2. One confirmation arrives twice: only one business outcome is recorded.
  3. An older event arrives after a newer one: the valid later state survives.
  4. The worker restarts halfway through: it finds the durable operation and resumes safely.
  5. The integration stays unavailable: staff receive a useful next step without seeing secrets.

Start with controlled test data and a simulated provider, then verify the authorized sandbox boundary. Add these scenarios to your automated test plan. Follow OWASP's logging guidance when deciding what to redact. A timestamped operational summary is more useful for support than a large dump nobody can safely share.

Does every API call need a background queue? No. A quick read may need only a timeout and understandable feedback. Work that is slow, costly, or must continue after the browser closes benefits from persistent tracking and recovery. Choose the architecture according to the consequence of failure. The goal is not to promise uninterrupted providers; it is to make interrupted work explainable and recoverable.

Prepared with AI assistance; technical references are included for verification.