Safe retries
A customer taps Book twice and the connection drops. Here's how to make sure that's one booking, not two.
A customer on a train taps Book. The request goes out, the signal dies, nothing comes back.
Did it work? Neither you nor they can tell. Retry and you might double-book Thursday morning. Don't, and they turn up to a slot nobody's coming to.
Send an Idempotency-Key and the retry is safe. The first request with that key
happens. Any later request with the same key returns the same answer without doing
the work again.
curl -X POST https://api.hyphabase.com/api/modules/clients \
-u sk_live_xxx: \
-H "X-Location-Id: $HYPHABASE_LOCATION_ID" \
-H "Idempotency-Key: signup-4471" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Dana",
"last_name": "Whitfield",
"email": "dana@example.com",
"phone": "+15035550143"
}'Use it on anything that creates something, and on any update or delete where doing it twice would be worse than harmless.
Choosing a key
The key has to be unique to the thing you meant to do, and identical across every retry of it.
Good: something from your own system that names the operation.
signup-4471, booking-8891-invoice, job-2026-07-30-portland.
Also good: a UUID generated once, before the first attempt, and reused by every retry.
Wrong, and the most common mistake: generating a fresh UUID inside the retry loop. Every attempt then looks like a brand-new operation, which is exactly the problem you were trying to solve.
Reusing a key with different data is an error, not a surprise
If you send the same key with a different body, you get a 409 rather than silently
getting the old response back. So a key collision shows up as a loud failure instead
of wrong data — which is why deriving keys from your own ids is safe even if you
occasionally get the derivation wrong.
What gets remembered
Only successful responses. A request that failed with a 4xx or 5xx isn't stored,
so you can fix the payload and retry with the same key.
Two conflicts you might see:
| Response | Meaning |
|---|---|
409 — already used with a different request | That key belongs to a different operation. Use a new one. |
409 — being processed | Two attempts raced. Wait a moment and retry; you'll get the winner's answer. |
In practice
Generate the key before the first attempt, and keep it for the life of that form submission — so a double-tap or a flaky connection can't create the same customer twice.
function SignupForm() {
// One key per form instance, not per submit attempt.
const idempotencyKey = useMemo(() => crypto.randomUUID(), []);
async function submit(values) {
return fetch('https://api.hyphabase.com/api/modules/clients', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-Location-Id': locationId,
'X-XSRF-TOKEN': readCookie('XSRF-TOKEN'),
'Idempotency-Key': idempotencyKey,
},
body: JSON.stringify(values),
});
}
}Disabling the button while in flight helps, but it doesn't survive a page reload or a browser retry. The key does.
Not the same as webhook deduplication
This protects requests you send us. Messages we send you are a separate problem with a separate solution — they can arrive more than once, and you deduplicate on the delivery id. See When deliveries fail.