Beta
Download

How do retries avoid charging a card twice?

AutoRouter5 min read

Key facts

A retry avoids a second charge only when the server can recognize it as the first request again. Stripe's idempotency keys do that inside Stripe, and a billing system's own record of each charge does it everywhere else. AutoRouter, the Mac app for Codex and Claude Code, builds its billing on that pattern: a retried seat change reuses the charge it already made, and an account holds one Pro subscription even when two checkouts start at once. AutoRouter wrote this post, and Stripe's behavior in it comes from Stripe's docs as read on November 4, 2026.

Why can a retry charge a card twice?

A retry can charge twice because a request can fail after the server has already done the work. When a connection drops mid-request, the client can't tell whether the charge went through, and a blind retry repeats it. Stripe's engineering blog describes these ambiguous failures and pairs idempotency keys with exponential backoff and jitter, so retries from many clients don't arrive at a struggling server all at once (Stripe, February 22, 2017).

What does Stripe save under an idempotency key?

For API v1, Stripe saves the status code and body of the first request made with a key once the endpoint starts running, and every retry with that key gets the saved response back, 500 errors included (Stripe: Idempotent requests). Stripe also compares each retry's parameters with the first request's and returns an error when they differ. A request that fails validation, or that collides with another request still running, isn't saved, so the client can retry it. With API v2, retries don't repeat work that succeeded, and Stripe tries to finish work that failed or partly failed.

What happened to the first request (API v1)What a retry with the same key gets
It succeededThe saved success response
It returned a 500The same saved 500
The retry's parameters differAn error
It failed validation or hit a concurrent requestA fresh attempt, since nothing was saved
Its key was pruned after 24 hours or moreA new request

How long does an idempotency key protect a retry?

At Stripe, an idempotency key protects retries for at least 24 hours: keys can be pruned once they are at least 24 hours old, and reusing a pruned key creates a new request (Stripe). Resend's email API works the same way, checking for an email sent with the same key in the last 24 hours (Resend). A job that keeps retrying for days outlives its key, so it needs its own record of what already happened.

What should a billing system record before it charges?

A billing system should record each charge before it calls the payment provider, so every retry finds the same charge and resumes it. Four steps cover it:

  1. Save the charge, with its amount and account, before calling the provider and outside any database transaction that can roll back.
  2. Derive one idempotency key for each step from that record's ID, so every retry can rebuild the same keys.
  3. On every retry, resume the saved charge with the same keys.
  4. Mark the charge done only after the provider's answer is saved.

Step 2 has one catch. Because Stripe replays a saved 500 to every retry with the same key, a step that failed with a server error needs a new key before it can succeed, and the system should first check what already went through so the new attempt doesn't repeat it.

What does AutoRouter do so you're charged once?

AutoRouter's billing is built so each payment happens once and the credit you get matches what you paid:

The same rule covers email. A waitlist follow-up is claimed before it sends, and a send that may have gone out is retried with the same key only within 23 hours, inside Resend's 24-hour window, so each follow-up sends once. For the rest of a team rollout, from sign-in to budgets, see How to roll out AI coding tools to an engineering team.

Questions

Does a retry with the same idempotency key ever charge twice?

Not while Stripe still holds the key, because a retry with that key gets the first request's saved result. Once a key is pruned, which Stripe may do after 24 hours, the same key starts a new request, and a retry with a different key is always a new request (Stripe).

What makes a good idempotency key?

Stripe suggests V4 UUIDs or another random string with enough entropy to avoid collisions, up to 255 characters, and advises against sensitive data such as email addresses (Stripe). Deriving the key from your own charge record's ID lets every retry rebuild the same key.

Why does a retried Stripe request return the same error?

With Stripe's API v1, the first response under a key is saved even when it is a 500, so retries with that key get the same error back. Requests that failed validation or collided with a concurrent request weren't saved and can be retried as they are (Stripe).

What does AutoRouter bill for?

AutoRouter Free is $0 a month and routes across your ChatGPT and Claude plans. Pro is $50 a month with $20 of included usage each month, and paid models are billed at the provider's price plus 10%, from included usage first and then from credits you top up. Credits you buy never expire. How to cut Claude Code and Codex costs covers the other ways to spend less.

Sources