A blind retry loop is a common reason an integration makes a short outage worse instead of surviving it: it keeps hammering an already-invalid key, burns the rate limit on calls doomed from the start, or, worse, charges the same purchase twice. The right approach starts not with a pause between attempts but with classifying the failure — not every error deserves a retry, and not every retry is safe.

Three categories of API errors

Temporary errors have nothing to do with the request itself: a dropped connection, a timeout, a brief overload on the provider's side. The same request with the same parameters succeeds a few seconds later, because the defect was in the channel, not the call.

Permanent errors are the opposite: an invalid key, a malformed parameter, missing permission. Such a call fails identically on attempt one and attempt one hundred, because the cause sits in the request or the account setup. Retrying without fixing it adds nothing but another log line.

Money errors look temporary — insufficient balance, no number available — but need separate handling. A later attempt can genuinely succeed once the balance is topped up, but retrying blindly, especially the charge itself, is unsafe — the next sections explain why.

What to retry and what not to

The rule is simple: only retry what can fix itself with time. A dropped connection, a timeout, a 5xx response, or an overload signal are candidates for an automatic backoff schedule. An invalid key, a bad parameter, a permissions error are not worth touching again until fixed manually — correct the setting and resend once, not loop. A balance or stock shortage should surface as a distinct state, decided by business logic or the user, not a timer. Where stock shortages tend to show up is covered in OTP-activation versus number rental.

Exponential backoff and the cost of a blind loop

Exponential backoff is a pause that grows each time — roughly a second, then two, four, eight — up to a cap of three to five attempts, after which the failure is passed up as final. Add a small random jitter so many clients hitting the same outage do not slam the provider with a synchronized wave right as it recovers.

Without classification up front, backoff does not help: applied to a permanent cause, it only stretches out useless calls, each occupying a slot in the account's shared rate limit. While the loop hammers an invalid key, legitimate calls from the same account hit that same limit and start failing on throttling instead. In automation with parallel workers, covered in registration automation via API, one stuck process can sink the limit for everyone else.

Logs for an incident review

An incident is reviewed from logs, not memory, so log enough to answer "was this the provider or us" without reproducing the problem. Minimum set: call time, endpoint, request or activation identifier, the provider's error code and message, the attempt number, and parameters minus secrets — keys and tokens never go into a log.

Also record how your handler classified the cause. If a permanent cause was treated as temporary, that only shows up in this record, not in a single response code.

Idempotency for money operations: avoiding a double charge

A timeout does not say whether the operation completed: the connection can drop right after the charge went through but before the response reached the client. Sending that request again without protection looks like a brand-new operation to the API — and charges the same purchase twice.

An idempotency key solves this: the client generates a unique identifier once per operation and sends it with every attempt, including ones after a timeout. The server remembers the result already returned for that key and, on a repeat, returns it instead of executing the operation again. This is the only safe way to resend a money call — without the key, every resend risks a double charge.

Frequently Asked Questions

Should a 429 be treated the same way as a timeout?

Yes, but with a longer pause: 429 means an exhausted rate limit, not a failure, and an immediate resend only extends the wait for everyone. Use the response header's value if the provider sends one, or start at several seconds instead of one.

Can a balance-charging call be repeated automatically after a timeout?

Not the raw request — only the operation, carried by an idempotency key. A timeout does not say whether it completed before the connection dropped; sending it again with the same key is safe, since the provider returns the already-executed result instead of charging again. Without the key, that risks a duplicate charge.

How many attempts is reasonable for a temporary error?

Typically three to five, with a wait ceiling of roughly 30-60 seconds. More rarely changes the outcome: if the provider has not recovered within that window, the problem is systemic and worth escalating, not looping further.

Error codes by category, the idempotency key header, and backoff parameters are in the API documentation. Webhook delivery, built on the same logic, is covered in webhooks: receiving codes without polling; basic integration is in the virtual number API guide.