ADR 0005: Idempotency Keys, and Why a Reused Key Is a Conflict
Why retrying a payout returns the original response, why the same key with a different body is a 409 rather than a cache hit, and why signatures carry a nonce.
Payment APIs are retried: networks time out, clients crash after sending and before reading, and callers retry on 5xx. A retry that creates a second payout is the worst failure a payments API has.
Decision
Callers supply an Idempotency-Key. The gateway stores the key with a hash of the request and the response, scoped per tenant, and distinguishes three cases:
Situation
Behaviour
Same key, same body
replay the stored response; the handler runs once
Same key, different body
409 idempotency_conflict
Same key, still in flight
409 in_progress
Handler threw
key released — a failure must stay retryable
Request signatures additionally carry a per-request nonce.
Consequences
Good. A retry is safe, and the client gets the original outcome rather than a second effect. Downstream idempotency reinforces it: the chain driver and the bank rails are both idempotent on the caller’s key, so even a partially completed saga does not double-broadcast or double-pay.The conflict case is the one that matters. Silently returning the first response when the body differs would hide a client bug behind a success, and the client would never learn its second, different request never happened.The nonce was not in the original design. Signatures covering only method, path, timestamp and body meant two identical requests in the same millisecond produced the same signature — so replay protection rejected a legitimate idempotent retry. Found by writing the retry test, not by reasoning about it.Costs. Keys need storage and expiry (24h). Clients that omit the key get no protection, and the API cannot force them.
Alternatives
Server-derived idempotency from a content hash alone — rejected. Two genuinely distinct transfers with identical parameters are legitimate; only the caller knows whether it means “again” or “the same one”.Returning the first response on a body mismatch — rejected as above: it converts a client bug into a silent wrong outcome.
{"docsConfig":{"theme":"palm","$schema":"https://mintlify.com/docs.json","name":"Arc","description":"Arc is a simulation of cross-border stablecoin and fiat infrastructure for the EU↔Africa corridor — and a field guide to how stablecoin payments actually work.","colors":{"primary":"#14B8A6","light":"#2DD4BF","dark":"#0F766E"},"logo":{"light":"https://mintcdn.com/arc-doc/m0ZoL8ScOCykrqmD/logo/arc-light.svg?fit=max\u0026auto=format\u0026n=m0ZoL8ScOCykrqmD\u0026q=85\u0026s=90e2e1d537056757a14f4f611ff78d11","dark":"https://mintcdn.com/arc-doc/m0ZoL8ScOCykrqmD/logo/arc-dark.svg?fit=max\u0026auto=format\u0026n=m0ZoL8ScOCykrqmD\u0026q=85\u0026s=58a04971d1ea14b20bd347e971014244","href":"/"},"favicon":"https://mintcdn.com/arc-doc/m0ZoL8ScOCykrqmD/logo/mark.svg?fit=max\u0026auto=format\u0026n=m0ZoL8ScOCykrqmD\u0026q=85\u0026s=24cce491a91f6bde7dfc139f0b283c8e","appearance":{"default":"dark"},"navbar":{"links":[{"href":"/start/quickstart","label":"Run it locally"},{"href":"https://github.com/el-uno/fintech_arc/blob/main/docs/BUILD_PLAN.md","label":"Build plan"}],"primary":{"type":"github","href":"https://github.com/el-uno/fintech_arc"}},"footer":{"socials":{"github":"https://github.com/el-uno/fintech_arc"}},"search":{"prompt":"Search the corridor"},"fonts":{"heading":{"family":"Inter"},"body":{"family":"Inter"}},"styling":{"eyebrows":"breadcrumbs","codeblocks":"dark","latex":true},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"arc-doc","hasAudioNarration":false,"actualSubdomain":"arc-doc","customDomains":["arc.udokaam.dev"],"gitSource":{"type":"github","owner":"udoka-am","repo":"fintech_arc","deployBranch":"main","contentDirectory":"apps/docs-site","isPrivate":false,"mountPath":"","label":""},"entitlements":{},"buildId":"6aea22181e900d99c6189d28:success","clientVersion":"0.0.4128","searchFilterCounts":[{"language":"default","tag":"Architecture","count":23},{"language":"default","tag":"IRL Scenarios","count":16},{"language":"default","tag":"Guide","count":10},{"language":"default","tag":"Decisions","count":9},{"language":"default","tag":"Practice","count":7},{"language":"default","tag":"The corridor problem","count":6},{"language":"default","tag":"The core","count":5},{"language":"default","tag":"Foundations","count":5},{"language":"default","tag":"Work through it","count":5},{"language":"default","tag":"Start here","count":4}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":false,"enableContextualStarterQuestions":false,"enableAskAiSearchBar":true},"basePath":"","hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-rbac-roles","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"rbac-expansion","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"full-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-dual-write","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"approve-review-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-metadata","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"sso-setup-deadline-banner","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-serve","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"live-agents","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":false},"customCss":[{"filePath":"style.css","content":"/* cyrillic-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');\n unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');\n unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');\n unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');\n unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;\n}\n/* vietnamese */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');\n unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');\n unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');\n unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n/* cyrillic-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');\n unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');\n unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');\n unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');\n unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;\n}\n/* vietnamese */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');\n unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');\n unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');\n unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n/* cyrillic-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');\n unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');\n unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');\n unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');\n unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;\n}\n/* vietnamese */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');\n unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');\n unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');\n unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n/* cyrillic-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 700;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');\n unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 700;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');\n unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 700;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');\n unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 700;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');\n unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;\n}\n/* vietnamese */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 700;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');\n unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 700;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');\n unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 700;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');\n unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n/* cyrillic-ext */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx3cwhsk.woff2) format('woff2');\n unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxTcwhsk.woff2) format('woff2');\n unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxPcwhsk.woff2) format('woff2');\n unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;\n}\n/* vietnamese */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx_cwhsk.woff2) format('woff2');\n unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx7cwhsk.woff2) format('woff2');\n unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxDcwg.woff2) format('woff2');\n unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n/* cyrillic-ext */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx3cwhsk.woff2) format('woff2');\n unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxTcwhsk.woff2) format('woff2');\n unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxPcwhsk.woff2) format('woff2');\n unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;\n}\n/* vietnamese */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx_cwhsk.woff2) format('woff2');\n unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx7cwhsk.woff2) format('woff2');\n unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxDcwg.woff2) format('woff2');\n unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n/* cyrillic-ext */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx3cwhsk.woff2) format('woff2');\n unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxTcwhsk.woff2) format('woff2');\n unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxPcwhsk.woff2) format('woff2');\n unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;\n}\n/* vietnamese */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx_cwhsk.woff2) format('woff2');\n unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx7cwhsk.woff2) format('woff2');\n unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxDcwg.woff2) format('woff2');\n unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n:root{--arc-teal: #2dd4bf;--arc-teal-deep: #14b8a6;--arc-amber: #f59e0b;--arc-rose: #fb7185;--arc-ground: #0d1117;--arc-raised: #151b23;--arc-sunken: #090d13;--arc-hairline: #232b36;--arc-ink: #e6edf3;--arc-ink-dim: #9aa7b4;--arc-ink-faint: #6b7785;--arc-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace}.light{--arc-ground: #ffffff;--arc-raised: #f7f9fa;--arc-sunken: #eef2f5;--arc-hairline: #dfe5ea;--arc-ink: #0d1117;--arc-ink-dim: #4a5561;--arc-ink-faint: #78838f}body{font-feature-settings:'cv05', 'ss01', 'tnum';letter-spacing:-0.006em}h1,h2,h3,h4{letter-spacing:-0.024em;font-weight:650}h1{letter-spacing:-0.032em}h2::before{content:'▚ ';color:var(--arc-teal);font-family:var(--arc-mono);font-weight:500;opacity:0.55}table{font-variant-numeric:tabular-nums}table td:not(:first-child),table th:not(:first-child){text-align:right;font-family:var(--arc-mono);font-size:0.86em}table td:first-child{font-family:var(--arc-mono);font-size:0.86em}code,pre,kbd{font-family:var(--arc-mono);font-feature-settings:'liga' 0}:not(pre)>code{border:1px solid var(--arc-hairline);border-radius:4px;background:var(--arc-sunken);color:var(--arc-teal);padding:0.1em 0.36em;font-size:0.86em;font-weight:500}pre{border:1px solid var(--arc-hairline);border-radius:6px;box-shadow:none}.arc-eyebrow{display:block;font-family:var(--arc-mono);font-size:0.7rem;font-weight:600;letter-spacing:0.14em;text-transform:uppercase;color:var(--arc-teal);margin-bottom:0.6rem}.arc-eyebrow--amber{color:var(--arc-amber)}.arc-eyebrow--dim{color:var(--arc-ink-faint)}.arc-stats{display:grid;grid-template-columns:repeat(auto-fit, minmax(150px, 1fr));gap:1px;background:var(--arc-hairline);border:1px solid var(--arc-hairline);border-radius:6px;overflow:hidden;margin:1.75rem 0}.arc-stat{background:var(--arc-raised);padding:1rem 1.1rem}.arc-stat__value{display:block;font-family:var(--arc-mono);font-size:1.45rem;font-weight:600;line-height:1.1;color:var(--arc-teal);font-variant-numeric:tabular-nums}.arc-stat__label{display:block;margin-top:0.4rem;font-size:0.78rem;line-height:1.4;color:var(--arc-ink-dim)}.arc-stat__source{display:block;margin-top:0.35rem;font-family:var(--arc-mono);font-size:0.66rem;letter-spacing:0.04em;color:var(--arc-ink-faint)}.arc-claim{border-left:2px solid var(--arc-teal);background:linear-gradient(90deg, rgba(45, 212, 191, 0.06), transparent 70%);padding:0.9rem 1.1rem;margin:1.6rem 0;border-radius:0 5px 5px 0}.arc-gap{border-left:2px solid var(--arc-amber);background:linear-gradient(90deg, rgba(245, 158, 11, 0.06), transparent 70%);padding:0.9rem 1.1rem;margin:1.6rem 0;border-radius:0 5px 5px 0}.arc-claim p:last-child,.arc-gap p:last-child{margin-bottom:0}.arc-symptom{font-size:1.08rem;line-height:1.6;color:var(--arc-ink);border:1px solid var(--arc-hairline);border-radius:6px;background:var(--arc-sunken);padding:1.15rem 1.3rem;margin:1.5rem 0 2rem}.arc-symptom::before{content:'SYMPTOM';display:block;font-family:var(--arc-mono);font-size:0.66rem;letter-spacing:0.16em;color:var(--arc-rose);margin-bottom:0.55rem}.arc-cite{font-size:0.82rem;color:var(--arc-ink-faint);font-family:var(--arc-mono)}.mermaid{background:var(--arc-sunken);border:1px solid var(--arc-hairline);border-radius:6px;padding:1rem}"}],"page":{"pagePath":"decisions/0005-idempotency-strategy","slug":"/decisions/0005-idempotency-strategy","pageMetadata":{"title":"ADR 0005: Idempotency Keys, and Why a Reused Key Is a Conflict","sidebarTitle":"ADR 0005","description":"Why retrying a payout returns the original response, why the same key with a different body is a 409 rather than a cache hit, and why signatures carry a nonce.","icon":"rotate-right","keywords":["idempotency key","safe retries","409 conflict","request signing nonce","payment API retry semantics"],"href":"/decisions/0005-idempotency-strategy"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"Why retrying a payout returns the original response, why the same key with a different body is a 409 rather than a cache hit, and why signatures carry a nonce.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={code:`code`,em:`em`,p:`p`,strong:`strong`,tbody:`tbody`,td:`td`,th:`th`,thead:`thead`,tr:`tr`,..._provideComponents(),...props.components},{Heading,Info,Table}=_components;if(!Heading)_missingMdxReference(`Heading`,true);if(!Info)_missingMdxReference(`Info`,true);if(!Table)_missingMdxReference(`Table`,true);return _jsxs(_Fragment,{children:[_jsx(`span`,{className:`arc-eyebrow`,children:`ADR 0005 · Accepted · Phase 6`}),`\n`,_jsx(Info,{children:_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Status:`}),` Accepted · `,_jsx(_components.strong,{children:`Phase:`}),` 6 · `,_jsx(_components.strong,{children:`Supersedes:`}),` none · `,_jsx(_components.strong,{children:`Superseded by:`}),` none`]})}),`\n`,_jsx(Heading,{level:`2`,id:`context`,children:`Context`}),`\n`,_jsx(_components.p,{children:`Payment APIs are retried: networks time out, clients crash after sending and before reading, and callers retry on 5xx. A retry that creates a second payout is the worst failure a payments API has.`}),`\n`,_jsx(Heading,{level:`2`,id:`decision`,children:`Decision`}),`\n`,_jsxs(_components.p,{children:[`Callers supply an `,_jsx(_components.code,{children:`Idempotency-Key`}),`. The gateway stores the key with a `,_jsx(_components.strong,{children:`hash of the request`}),` and the response, scoped per tenant, and distinguishes three cases:`]}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Situation`}),_jsx(_components.th,{children:`Behaviour`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Same key, same body`}),_jsx(_components.td,{children:`replay the stored response; the handler runs once`})]}),_jsxs(_components.tr,{children:[_jsxs(_components.td,{children:[`Same key, `,_jsx(_components.strong,{children:`different`}),` body`]}),_jsx(_components.td,{children:_jsx(_components.code,{children:`409 idempotency_conflict`})})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Same key, still in flight`}),_jsx(_components.td,{children:_jsx(_components.code,{children:`409 in_progress`})})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Handler threw`}),_jsx(_components.td,{children:`key released — a failure must stay retryable`})]})]})]}),`\n`,_jsxs(_components.p,{children:[`Request signatures additionally carry a `,_jsx(_components.strong,{children:`per-request nonce`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`consequences`,children:`Consequences`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Good.`}),` A retry is safe, and the client gets the original outcome rather than a second effect. Downstream idempotency reinforces it: the chain driver and the bank rails are both idempotent on the caller’s key, so even a partially completed saga does not double-broadcast or double-pay.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`The conflict case is the one that matters.`}),` Silently returning the first response when the body differs would hide a client bug behind a success, and the client would never learn its second, different request never happened.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`The nonce was not in the original design.`}),` Signatures covering only method, path, timestamp and body meant two identical requests in the same millisecond produced the same signature — so replay protection rejected a legitimate idempotent retry. Found by writing the retry test, not by reasoning about it.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Costs.`}),` Keys need storage and expiry (24h). Clients that omit the key get no protection, and the API cannot force them.`]}),`\n`,_jsx(Heading,{level:`2`,id:`alternatives`,children:`Alternatives`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.em,{children:`Server-derived idempotency from a content hash alone`}),` — rejected. Two genuinely distinct transfers with identical parameters are legitimate; only the caller knows whether it means “again” or “the same one”.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.em,{children:`Returning the first response on a body mismatch`}),` — rejected as above: it converts a client bug into a silent wrong outcome.`]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"codeStyling":"dark","config":{},"pageMetadata":{"title":"ADR 0005: Idempotency Keys, and Why a Reused Key Is a Conflict","sidebarTitle":"ADR 0005","description":"Why retrying a payout returns the original response, why the same key with a different body is a 409 rather than a cache hit, and why signatures carry a nonce.","icon":"rotate-right","keywords":["idempotency key","safe retries","409 conflict","request signing nonce","payment API retry semantics"],"href":"/decisions/0005-idempotency-strategy"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"Context","slug":"context","depth":2,"children":[]},{"title":"Decision","slug":"decision","depth":2,"children":[]},{"title":"Consequences","slug":"consequences","depth":2,"children":[]},{"title":"Alternatives","slug":"alternatives","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"arc-doc","siteName":"Arc","title":"ADR 0005: Idempotency Keys, and Why a Reused Key Is a Conflict - Arc","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-08-14T01:20:45.565Z","originalFileLocation":"decisions/0005-idempotency-strategy.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"dark\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"Why retrying a payout returns the original response, why the same key with a different body is a 409 rather than a cache hit, and why signatures carry a nonce.\">\u003cmeta data-mint-head name=\"application-name\" content=\"Arc\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/arc-doc/8qf7D6aInLMuoIah/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/arc-doc/8qf7D6aInLMuoIah/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/arc-doc/8qf7D6aInLMuoIah/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/arc-doc/8qf7D6aInLMuoIah/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/arc-doc/8qf7D6aInLMuoIah/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/arc-doc/8qf7D6aInLMuoIah/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/arc-doc/8qf7D6aInLMuoIah/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/arc-doc/8qf7D6aInLMuoIah/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/arc-doc/8qf7D6aInLMuoIah/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"ADR 0005: Idempotency Keys, and Why a Reused Key Is a Conflict - Arc\">\u003cmeta data-mint-head property=\"og:description\" content=\"Why retrying a payout returns the original response, why the same key with a different body is a 409 rather than a cache hit, and why signatures carry a nonce.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Arc\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://arc.udokaam.dev/decisions/0005-idempotency-strategy\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://arc-doc.mintlify.site/_mintlify/api/og/v2?division=Decisions\u0026amp;title=ADR+0005%3A+Idempotency+Keys%2C+and+Why+a+Reused+Key+Is+a+Conflict\u0026amp;description=Why+retrying+a+payout+returns+the+original+response%2C+why+the+same+key+with+a+different+body+is+a+409+rather+than+a+cache+hit%2C+and+why+signatures+carry+a+nonce.\u0026amp;theme=a62ca8cb35008bd1421320f2\u0026amp;signature=021686eaec9261a39e82dab83907594a827c3778888e17369acc187f54005f2e\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"ADR 0005: Idempotency Keys, and Why a Reused Key Is a Conflict - Arc\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"Why retrying a payout returns the original response, why the same key with a different body is a 409 rather than a cache hit, and why signatures carry a nonce.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://arc-doc.mintlify.site/_mintlify/api/og/v2?division=Decisions\u0026amp;title=ADR+0005%3A+Idempotency+Keys%2C+and+Why+a+Reused+Key+Is+a+Conflict\u0026amp;description=Why+retrying+a+payout+returns+the+original+response%2C+why+the+same+key+with+a+different+body+is+a+409+rather+than+a+cache+hit%2C+and+why+signatures+carry+a+nonce.\u0026amp;theme=a62ca8cb35008bd1421320f2\u0026amp;signature=021686eaec9261a39e82dab83907594a827c3778888e17369acc187f54005f2e\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/arc-doc/8qf7D6aInLMuoIah/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Arc\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#14B8A6\">\u003clink data-mint-head rel=\"canonical\" href=\"https://arc.udokaam.dev/decisions/0005-idempotency-strategy\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/decisions/0005-idempotency-strategy.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://arc.udokaam.dev/#organization\",\"name\":\"Arc\",\"url\":\"https://arc.udokaam.dev\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/arc-doc/m0ZoL8ScOCykrqmD/logo/arc-light.svg?fit=max\u0026auto=format\u0026n=m0ZoL8ScOCykrqmD\u0026q=85\u0026s=90e2e1d537056757a14f4f611ff78d11\"}},{\"@type\":\"WebSite\",\"@id\":\"https://arc.udokaam.dev#website\",\"name\":\"Arc\",\"url\":\"https://arc.udokaam.dev\",\"publisher\":{\"@id\":\"https://arc.udokaam.dev/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://arc.udokaam.dev/decisions/0005-idempotency-strategy#webpage\",\"url\":\"https://arc.udokaam.dev/decisions/0005-idempotency-strategy\",\"name\":\"ADR 0005: Idempotency Keys, and Why a Reused Key Is a Conflict\",\"description\":\"Why retrying a payout returns the original response, why the same key with a different body is a 409 rather than a cache hit, and why signatures carry a nonce.\",\"dateModified\":\"2026-08-14T01:20:45.565Z\",\"isPartOf\":{\"@id\":\"https://arc.udokaam.dev#website\"},\"breadcrumb\":{\"@id\":\"https://arc.udokaam.dev/decisions/0005-idempotency-strategy#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://arc.udokaam.dev/decisions/0005-idempotency-strategy#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Decisions\",\"item\":\"https://arc.udokaam.dev/decisions\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"ADR 0005: Idempotency Keys, and Why a Reused Key Is a Conflict\",\"item\":\"https://arc.udokaam.dev/decisions/0005-idempotency-strategy\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://arc.udokaam.dev/decisions/0005-idempotency-strategy#article\",\"headline\":\"ADR 0005: Idempotency Keys, and Why a Reused Key Is a Conflict\",\"name\":\"ADR 0005: Idempotency Keys, and Why a Reused Key Is a Conflict\",\"description\":\"Why retrying a payout returns the original response, why the same key with a different body is a 409 rather than a cache hit, and why signatures carry a nonce.\",\"url\":\"https://arc.udokaam.dev/decisions/0005-idempotency-strategy\",\"mainEntityOfPage\":{\"@id\":\"https://arc.udokaam.dev/decisions/0005-idempotency-strategy#webpage\"},\"dateModified\":\"2026-08-14T01:20:45.565Z\",\"keywords\":\"idempotency key, safe retries, 409 conflict, request signing nonce, payment API retry semantics\",\"publisher\":{\"@id\":\"https://arc.udokaam.dev/#organization\"},\"isPartOf\":{\"@id\":\"https://arc.udokaam.dev#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"Arc\",\"publisher\":{\"@type\":\"Organization\",\"name\":\"Arc\"},\"provider\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","agentPointerHref":"/decisions/0005-idempotency-strategy.md","siteName":"Arc","nav":{"global":null,"tabs":[{"tab":"Guide","pages":[{"title":"Guide","href":""}]},{"tab":"Architecture","groups":[{"group":"Foundations","icon":"layer-group","pages":[{"title":"Arc Architecture Overview: Six Contexts, One Deployable","sidebarTitle":"Overview","icon":"layer-group","href":"/architecture/overview"},{"title":"Why Every Monetary Value in Arc Is a Bigint Amount","sidebarTitle":"Money","icon":"calculator","href":"/architecture/money"},{"title":"Contexts, Events, and the Boundary That Fails Builds","sidebarTitle":"Events","icon":"inbox","href":"/architecture/contexts-and-events"},{"title":"Platform and Security: Signing, Idempotency, Secrets","sidebarTitle":"Platform","icon":"shield-halved","href":"/architecture/security"},{"title":"The Last Mile API: Partner Onboarding and Sandbox","sidebarTitle":"Partner platform","icon":"plug","href":"/architecture/partner-platform"}]},{"group":"The core","icon":"book-open","pages":[{"title":"The Arc Ledger: Double-Entry Accounting With No Tolerance","sidebarTitle":"Ledger","icon":"scale-balanced","href":"/architecture/ledger"},{"title":"The Chain Layer: One Interface, Five Different Physics","sidebarTitle":"Chain Layer","icon":"link","href":"/architecture/chain-layer"},{"title":"The Settlement Saga: Five Steps, Five Compensations","sidebarTitle":"Settlement Saga","icon":"rotate-left","href":"/architecture/settlement-saga"},{"title":"Risk and Compliance: A Blocking Gate on Money Movement","sidebarTitle":"Compliance","icon":"shield","href":"/architecture/compliance"},{"title":"Reconciliation: Balanced Books Are Not Correct Books","sidebarTitle":"Reconciliation","icon":"scale-unbalanced","href":"/architecture/reconciliation"}]},{"group":"Proof","icon":"flask","pages":[{"title":"What the Arc Test Suite Proves and How It Was Verified","sidebarTitle":"Testing","icon":"flask","href":"/architecture/testing"}]},{"group":"Flows","icon":"route","pages":[{"title":"Consumer Remittance: €1,000 From Munich to M-Pesa Kenya","sidebarTitle":"Consumer Flow","icon":"user","href":"/flows/consumer-remittance"},{"title":"Enterprise Payout: NGN Virtual Account to SEPA Instant","sidebarTitle":"Enterprise Flow","icon":"building","href":"/flows/enterprise-payout"},{"title":"Reversal: Four Compensating Journals, in Reverse Order","sidebarTitle":"Reversal","icon":"rotate-left","href":"/flows/reversal"}]},{"group":"Decisions","icon":"gavel","pages":[{"title":"Architecture Decision Records: How Arc Was Designed","sidebarTitle":"Overview","icon":"gavel","href":"/decisions/index"},{"title":"ADR 0001: Money as Integer Minor Units, Enforced by Lint","sidebarTitle":"ADR 0001","icon":"calculator","href":"/decisions/0001-money-as-integer-minor-units"},{"title":"ADR 0002: Modular Monolith Over Microservices, Enforced","sidebarTitle":"ADR 0002","icon":"cubes","href":"/decisions/0002-modular-monolith"},{"title":"ADR 0003: A Chain-Agnostic Driver With a Deterministic Simulator","sidebarTitle":"ADR 0003","icon":"link","href":"/decisions/0003-chain-abstraction"},{"title":"ADR 0004: A Compensating Saga Rather Than Two-Phase Commit","sidebarTitle":"ADR 0004","description":"Why a corridor transfer is a saga with a compensating action per step, given that banks and blockchains do not participate in distributed transactions.","icon":"diagram-project","href":"/decisions/0004-saga-over-two-phase-commit"},{"title":"ADR 0005: Idempotency Keys, and Why a Reused Key Is a Conflict","sidebarTitle":"ADR 0005","icon":"rotate-right","href":"/decisions/0005-idempotency-strategy"},{"title":"ADR 0006: A Transactional Outbox for At-Least-Once Delivery","sidebarTitle":"ADR 0006","description":"Why events are staged in the same transaction as the state change they describe, and why every handler must therefore be idempotent.","icon":"inbox","href":"/decisions/0006-transactional-outbox"},{"title":"ADR 0007: Row-Level Tenancy With Environment-Prefixed Credentials","sidebarTitle":"ADR 0007","icon":"users-between-lines","href":"/decisions/0007-multi-tenancy-model"},{"title":"ADR 0008: Double-Entry Accounting, Enforced by the Database","sidebarTitle":"ADR 0008","icon":"scale-balanced","href":"/decisions/0008-double-entry-ledger"}]}]},{"tab":"IRL Scenarios","pages":[{"title":"IRL Scenarios","href":"/stories"}]},{"tab":"Practice","pages":[{"title":"Practice","href":"/practice"}]}]},"basePath":"","title":"ADR 0005: Idempotency Keys, and Why a Reused Key Is a Conflict - Arc","buildId":"dpl_5tKr6cUUeuVJdEZ9JAEY3h4soMJ8","deploymentId":"dpl_5tKr6cUUeuVJdEZ9JAEY3h4soMJ8","dynamics":[],"chunks":[]}