Skip to content
Engineering

Payment API Timeouts: What to Do When You Don't Know If a Payment Went Through

A timeout doesn't mean a payment failed. It means you don't know. How timeouts differ from declines and pending payments, what a timeout can and can't tell you, how to set a timeout budget, which errors are safe to retry, and a status-check routine that resolves unknown payments without charging anyone twice.

LILPE Editorial Team9 October 2026 9 min read
Cover for an engineering guide to handling payment API timeouts and unknown payment outcomes

Most payment code is written for two outcomes: the payment succeeded, or it failed. Real systems have a third, and it causes most of the trouble: you asked, and no answer came back. The request might never have reached the provider. It might have reached it and been declined. Or the customer might have been charged a moment after your code stopped waiting.

How you handle that third outcome decides whether a slow afternoon at a provider becomes a few delayed confirmations or a pile of double charges and unpaid orders. This guide sets out what a timeout actually tells you, how to choose timeouts deliberately, which errors are safe to retry, and a routine for resolving unknown payments.

Timeout, decline, pending and error are four different things

The first rule is to keep these apart in your code and your data. Collapsing them into "failed" is the root of most timeout bugs.

Outcomes of a payment request, and what each tells you
OutcomeWhat you knowWhat to do
SuccessThe provider confirmed the paymentFulfil the order
DeclineThe provider or bank answered noShow the customer a clear message; another method may work
PendingThe provider answered: it's waiting, often for the customer or their bankWait for the webhook; check status on a schedule; don't ask the customer to pay again
Error with a responseThe provider answered with an error, such as invalid request or rate limitDepends on the error; see the retry table below
Timeout or lost connectionNothing. The outcome is unknownResolve the status before doing anything else

Pending deserves a special mention because it's often mistaken for a timeout. Many payments are asynchronous by design: a UPI collect request waits for the customer to enter their PIN, a card payment may wait for 3-D Secure, and a net banking payment waits for the customer on their bank's page. That's not a fault. The provider has told you it's waiting, and its webhook will tell you how it ended.

Where in the chain a timeout happens

A payment request crosses several systems before an answer comes back, and a delay at any of them can push your call past its timeout:

The hops a card or UPI payment request crosses before you get an answer
  1. Your serverCreates the payment
  2. ProviderGateway or aggregator
  3. Network or switchCard network or UPI switch
  4. BankIssuing or remitter bank decides
  5. ResponseTravels back the same way

You only see the first hop. If the issuing bank is slow, your provider is still waiting when your client gives up, and the payment may still complete. That's why a timeout on your side must never be read as a failure on theirs.

Not all timeouts are equal

Your HTTP client usually distinguishes two kinds of timeout, and they mean different things:

  • A connect timeout means the connection to the provider was never established. The request wasn't sent, so the payment can't have been created. Retrying is safe, ideally with the same idempotency key anyway.
  • A read timeout, or a connection dropped after the request was sent, means the provider may have received and acted on the request. The outcome is unknown.
  • A gateway timeout response, such as HTTP 504 from a proxy in front of the provider, means some server in the path gave up waiting for the one behind it. The request may well have been processed. Treat it like a read timeout.

If your client library can't tell you which kind happened, assume the worse one: unknown.

Set a timeout budget on purpose

Timeouts are often left at a library default, which is either far too long (requests hang and exhaust your workers) or too short (you give up on payments that would have succeeded). Set them deliberately, as a budget from the customer inwards:

  1. Start with how long the customer will wait on the checkout screen before you show a "still confirming" state. Everything behind it has to fit inside that.
  2. Set the call from your server to the provider shorter than that, so your server can answer the page in time, even if the answer is "pending".
  3. Use your providers' documentation for how long a create call can legitimately take, and measure it: look at the slowest few percent of response times per provider and method, not the average.
  4. Keep the connect timeout short. A provider that can't accept a connection quickly is usually having a wider problem.
  5. Never let a retry loop exceed the overall budget. Two retries of a call that already takes most of the budget will blow straight through it.

Which errors to retry

Retrying blindly turns a provider's bad minute into an outage, and retrying the wrong error creates duplicates. A sensible default by response:

A default retry policy for payment API responses
ResponseMeaningRetry?
400, 401, 403, 422Your request or credentials are wrongNo. Fix the request; retrying won't change the answer
409Conflict, often the same idempotency key still in progressLater, with the same key
429Rate limitedYes, after the delay in Retry-After if given, with the same key
503Provider temporarily unavailableYes, after a delay, with the same key
500, 502, 504Server or gateway error; the request may have been processedCheck status first, then retry with the same key
Connect timeoutRequest never sentYes, with the same key
Read timeoutUnknownCheck status first

Two rules apply to every retry. Use the same idempotency key, so a repeat can't create a second payment; our guide to duplicate payments shows how keys work. And wait between attempts with exponential backoff plus random jitter, so thousands of clients don't retry at the same instant and knock the provider over again as it recovers.

Resolving an unknown payment

When the outcome is unknown, the job is to find out what happened, not to try again. Every provider worth using offers a way to fetch a payment's status, by its own ID or by the reference you sent. That's why sending your own unique reference on every create call matters: it's how you find a payment whose response you never received.

  • Succeeded: mark the order paid, exactly as if the original response had arrived.
  • Failed: the attempt is over. It's now safe to offer the customer another try, or to route a retry to another provider.
  • Pending: wait for the webhook and keep checking on a schedule. Don't retry elsewhere; a pending payment can still succeed.
  • Not found: be careful. Some providers take a moment to make a new payment searchable. Only treat "not found" as "never created" when the provider documents that, and after its stated window has passed. Otherwise keep the attempt open and let reconciliation settle it.
TypeScriptResolving an attempt whose create call timed out (provider client and helpers omitted)
type Outcome = "succeeded" | "failed" | "pending" | "not_found";

// Never creates a new payment: it only asks what happened to this one.
async function resolveUnknown(attempt: Attempt): Promise<Outcome> {
  const delaysMs = [2_000, 5_000, 15_000, 60_000]; // example schedule
  for (const base of delaysMs) {
    await sleep(base / 2 + Math.random() * (base / 2)); // backoff with jitter
    try {
      const found = await provider.findByReference(attempt.reference);
      if (found.status !== "not_found") return found.status;
    } catch (err) {
      if (!isTransient(err)) throw err; // e.g. bad credentials: alert a person
    }
  }
  // Still unknown: leave it pending for the webhook, the scheduled
  // status job and reconciliation. Don't charge again.
  return "not_found";
}

What you do with the answer is straightforward:

Backstops: webhooks, scheduled checks and reconciliation

Status checks at request time resolve most unknowns. Three backstops catch the rest:

  • Webhooks deliver the final status when it happens, including for payments whose create call timed out. Verify and process them as our webhooks guide describes.
  • A scheduled job checks every attempt still pending or unknown after a set time and asks the provider for its status.
  • Daily reconciliation against the provider's reports finds anything both of those missed, such as a payment that succeeded after you marked it failed. Our reconciliation guide covers the routine.

What the customer sees

A timeout is an engineering problem, but the customer experiences it as uncertainty. Show a clear pending state, tell them not to pay again, and update the page when you know. If their account was debited and the payment ultimately failed, the money comes back: RBI's turnaround-time framework requires failed transactions to be reversed within set timelines, such as T + 5 days for a card-not-present payment where the account was debited but the merchant didn't receive confirmation, with compensation for delays beyond that. Our guide to money debited but the payment failed covers the timelines by payment method.

Measure timeouts, don't just handle them

Track timeouts and unknown outcomes per provider and payment method, alongside response-time percentiles. A rising timeout rate is often the first sign a provider or bank is struggling, well before success rates fall. Our guide to payment success rate explains how to keep timeouts and pending payments from distorting that number.

If you route across several providers, these signals also drive routing decisions. LILPE's smart retry page describes checking a timed-out attempt's status on the first provider before any retry, and not retrying pending attempts; its automatic failover page describes using timeout and error rates to pause an unhealthy provider for a method.

Frequently asked questions

Does a payment gateway timeout mean the payment failed?

No. It means your system didn't get an answer in time. The payment may have failed, may still be processing, or may have succeeded. Check its status with the provider, using your reference or the provider's ID, before showing a failure or trying again.

Should I retry a payment request after a timeout?

Only with the same idempotency key, and preferably after checking the payment's status. A retry without the key, or as a brand-new payment, can charge the customer twice if the first request went through.

What timeout should I set for payment API calls?

There's no single right number. Work back from how long the customer will wait at checkout, use your providers' documented processing times, and measure the slowest few percent of real responses per provider and method. Keep connect timeouts short and make sure retries fit inside the overall budget.

What is the difference between a pending payment and a timed-out one?

Pending is an answer: the provider told you the payment is waiting, usually for the customer or their bank, and will report the result. A timeout is the absence of an answer, so you have to ask for the status yourself.

What does HTTP 429 mean from a payment API?

Too many requests: you've hit the provider's rate limit. Wait, using the Retry-After value if the response includes one, then retry with the same idempotency key. Our guide to preparing payments for sale days covers rate limits in more depth.

Official sources

Last reviewed . Examples, amounts and screens marked illustrative are not LILPE figures.

Route every payment to where it succeeds.

Tell us which providers you use today and where payments get stuck. We'll show you what the setup would look like on LILPE.