[{"data":1,"prerenderedAt":574},["ShallowReactive",2],{"content-query-VbkiWUDvRE":3},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"layout":10,"crumb":11,"summary":12,"group":13,"order":14,"code":14,"updated":15,"cta":16,"related":24,"faq":42,"body":58,"_type":567,"_id":568,"_source":569,"_file":570,"_stem":571,"_extension":572,"sitemap":573},"/http-errors/409-conflict","http-errors",false,"","409 Conflict: what it means and how to fix it","HTTP 409 Conflict means the request clashes with the resource's current state. Causes, fixes, 409 vs 412 vs 422, and duplicate webhooks.","reference","409 Conflict","The request clashes with the resource's current state.","4xx client errors",409,"2026-10-28",{"eyebrow":17,"title":18,"text":19,"to":20,"label":21,"secondaryTo":22,"secondaryLabel":23},"Webhook Relay Functions","Drop duplicate webhooks before they reach your app","Webhook Relay functions have a built-in KV store. Key it on the provider's event ID: if the ID is already stored, stop forwarding; if not, store it with a TTL that covers the provider's retry window. No database or Redis to run.","/docs/webhooks/functions/kv-store/","Dedupe with the KV store","/blog/webhook-retries-and-idempotency/","Retries and idempotency",[25,28,31,34,37,40],{"title":26,"to":27},"Idempotency","/glossary/idempotency/",{"title":29,"to":30},"At-least-once delivery","/glossary/at-least-once-delivery/",{"title":32,"to":33},"400 vs 422","/http-errors/400-vs-422/",{"title":35,"to":36},"410 Gone","/http-errors/410-gone/",{"title":38,"to":39},"Webhook retries","/glossary/webhook-retries/",{"title":41,"to":22},"Webhook retries and idempotency",[43,46,49,52,55],{"q":44,"a":45},"What does 409 Conflict mean?","HTTP 409 Conflict means the request couldn't be completed because it conflicts with the current state of the target resource. Typical examples are creating something that already exists, updating a record someone else changed since you read it, or deleting a resource that other records still depend on.",{"q":47,"a":48},"How do I fix a 409 Conflict error?","Read the response body to see what conflicts. If the resource already exists, fetch and use it instead of creating it again. If it was changed since you read it, fetch the latest version, reapply your change and retry. If another request with the same idempotency key is still running, wait and retry.",{"q":50,"a":51},"What is the difference between 409 and 422?","422 means the data itself is invalid regardless of what's on the server, such as a malformed email address. 409 means the data would be fine on its own but conflicts with the server's current state, such as a username that's already taken or a stale version number.",{"q":53,"a":54},"Should a webhook endpoint return 409 for a duplicate event?","No. Return 200 for a duplicate you've already processed. Most providers treat any non-2xx response as a failure and keep retrying, so answering a duplicate with 409 creates more duplicates. Record processed event IDs and acknowledge repeats without processing them again.",{"q":56,"a":57},"Why does Stripe return 409?","Stripe returns 409 when a request conflicts with another one, most often when a second request reuses an idempotency key while the first request with that key is still in progress. Wait for the first request to finish, then retry with the same key to get its stored result.",{"type":59,"children":60,"toc":560},"root",[61,81,130,137,208,214,332,338,382,388,406,424,429,486,521,527,554],{"type":62,"tag":63,"props":64,"children":65},"element","p",{},[66,79],{"type":62,"tag":67,"props":68,"children":69},"strong",{},[70,77],{"type":62,"tag":71,"props":72,"children":74},"code",{"className":73},[],[75],{"type":76,"value":11},"text",{"type":76,"value":78}," means the request is valid, but it conflicts with the current state of the resource on the server.",{"type":76,"value":80}," Creating something that already exists, saving changes to a record someone else updated after you read it, or deleting a resource other records still depend on are all 409s. The fix is usually to fetch the current state, reconcile, and try again.",{"type":62,"tag":82,"props":83,"children":87},"pre",{"className":84,"code":85,"language":86,"meta":7,"style":7},"language-http shiki shiki-themes github-dark","HTTP/1.1 409 Conflict\nContent-Type: application/json\n\n{\"error\": \"already_exists\", \"message\": \"A user with email ana@example.com already exists\", \"existing_id\": \"usr_42\"}\n","http",[88],{"type":62,"tag":71,"props":89,"children":90},{"__ignoreMap":7},[91,102,111,121],{"type":62,"tag":92,"props":93,"children":96},"span",{"class":94,"line":95},"line",1,[97],{"type":62,"tag":92,"props":98,"children":99},{},[100],{"type":76,"value":101},"HTTP/1.1 409 Conflict\n",{"type":62,"tag":92,"props":103,"children":105},{"class":94,"line":104},2,[106],{"type":62,"tag":92,"props":107,"children":108},{},[109],{"type":76,"value":110},"Content-Type: application/json\n",{"type":62,"tag":92,"props":112,"children":114},{"class":94,"line":113},3,[115],{"type":62,"tag":92,"props":116,"children":118},{"emptyLinePlaceholder":117},true,[119],{"type":76,"value":120},"\n",{"type":62,"tag":92,"props":122,"children":124},{"class":94,"line":123},4,[125],{"type":62,"tag":92,"props":126,"children":127},{},[128],{"type":76,"value":129},"{\"error\": \"already_exists\", \"message\": \"A user with email ana@example.com already exists\", \"existing_id\": \"usr_42\"}\n",{"type":62,"tag":131,"props":132,"children":134},"h2",{"id":133},"common-causes",[135],{"type":76,"value":136},"Common causes",{"type":62,"tag":138,"props":139,"children":140},"ul",{},[141,152,178,188,198],{"type":62,"tag":142,"props":143,"children":144},"li",{},[145,150],{"type":62,"tag":67,"props":146,"children":147},{},[148],{"type":76,"value":149},"Duplicate creation.",{"type":76,"value":151}," A unique email, username, slug or external ID is already taken.",{"type":62,"tag":142,"props":153,"children":154},{},[155,160,162,168,170,176],{"type":62,"tag":67,"props":156,"children":157},{},[158],{"type":76,"value":159},"Concurrent edits.",{"type":76,"value":161}," Optimistic locking: the client sends a version number or ",{"type":62,"tag":71,"props":163,"children":165},{"className":164},[],[166],{"type":76,"value":167},"updated_at",{"type":76,"value":169}," that no longer matches the stored one. Kubernetes returns 409 when an update's ",{"type":62,"tag":71,"props":171,"children":173},{"className":172},[],[174],{"type":76,"value":175},"resourceVersion",{"type":76,"value":177}," is stale.",{"type":62,"tag":142,"props":179,"children":180},{},[181,186],{"type":62,"tag":67,"props":182,"children":183},{},[184],{"type":76,"value":185},"Idempotency key in use.",{"type":76,"value":187}," Stripe and similar APIs return 409 if a second request reuses an idempotency key while the first one is still running.",{"type":62,"tag":142,"props":189,"children":190},{},[191,196],{"type":62,"tag":67,"props":192,"children":193},{},[194],{"type":76,"value":195},"Invalid state transition.",{"type":76,"value":197}," Refunding a payment that was already refunded, or merging a pull request that has merge conflicts.",{"type":62,"tag":142,"props":199,"children":200},{},[201,206],{"type":62,"tag":67,"props":202,"children":203},{},[204],{"type":76,"value":205},"Dependent resources.",{"type":76,"value":207}," Deleting a project that still has active resources inside it.",{"type":62,"tag":131,"props":209,"children":211},{"id":210},"_409-vs-similar-codes",[212],{"type":76,"value":213},"409 vs similar codes",{"type":62,"tag":215,"props":216,"children":217},"table",{},[218,237],{"type":62,"tag":219,"props":220,"children":221},"thead",{},[222],{"type":62,"tag":223,"props":224,"children":225},"tr",{},[226,232],{"type":62,"tag":227,"props":228,"children":229},"th",{},[230],{"type":76,"value":231},"Code",{"type":62,"tag":227,"props":233,"children":234},{},[235],{"type":76,"value":236},"Use it when",{"type":62,"tag":238,"props":239,"children":240},"tbody",{},[241,258,291,315],{"type":62,"tag":223,"props":242,"children":243},{},[244,253],{"type":62,"tag":245,"props":246,"children":247},"td",{},[248],{"type":62,"tag":71,"props":249,"children":251},{"className":250},[],[252],{"type":76,"value":11},{"type":62,"tag":245,"props":254,"children":255},{},[256],{"type":76,"value":257},"The request clashes with the server's current state.",{"type":62,"tag":223,"props":259,"children":260},{},[261,270],{"type":62,"tag":245,"props":262,"children":263},{},[264],{"type":62,"tag":71,"props":265,"children":267},{"className":266},[],[268],{"type":76,"value":269},"412 Precondition Failed",{"type":62,"tag":245,"props":271,"children":272},{},[273,275,281,283,289],{"type":76,"value":274},"A conditional header (",{"type":62,"tag":71,"props":276,"children":278},{"className":277},[],[279],{"type":76,"value":280},"If-Match",{"type":76,"value":282},", ",{"type":62,"tag":71,"props":284,"children":286},{"className":285},[],[287],{"type":76,"value":288},"If-Unmodified-Since",{"type":76,"value":290},") didn't match. This is the standard code for ETag-based concurrency.",{"type":62,"tag":223,"props":292,"children":293},{},[294,303],{"type":62,"tag":245,"props":295,"children":296},{},[297],{"type":62,"tag":71,"props":298,"children":300},{"className":299},[],[301],{"type":76,"value":302},"422 Unprocessable Content",{"type":62,"tag":245,"props":304,"children":305},{},[306,308,313],{"type":76,"value":307},"The data is invalid on its own, whatever the server state. See ",{"type":62,"tag":309,"props":310,"children":311},"a",{"href":33},[312],{"type":76,"value":32},{"type":76,"value":314},".",{"type":62,"tag":223,"props":316,"children":317},{},[318,327],{"type":62,"tag":245,"props":319,"children":320},{},[321],{"type":62,"tag":71,"props":322,"children":324},{"className":323},[],[325],{"type":76,"value":326},"428 Precondition Required",{"type":62,"tag":245,"props":328,"children":329},{},[330],{"type":76,"value":331},"The server requires a conditional request and the client didn't send one.",{"type":62,"tag":131,"props":333,"children":335},{"id":334},"how-to-fix-a-409-as-a-client",[336],{"type":76,"value":337},"How to fix a 409 as a client",{"type":62,"tag":339,"props":340,"children":341},"ol",{},[342,352,362,372],{"type":62,"tag":142,"props":343,"children":344},{},[345,350],{"type":62,"tag":67,"props":346,"children":347},{},[348],{"type":76,"value":349},"Read the body.",{"type":76,"value":351}," A good API says what conflicts and often returns the ID of the existing resource.",{"type":62,"tag":142,"props":353,"children":354},{},[355,360],{"type":62,"tag":67,"props":356,"children":357},{},[358],{"type":76,"value":359},"If it already exists, use it.",{"type":76,"value":361}," For \"create if missing\" logic, treat 409 as success and fetch the existing record, or switch to an upsert endpoint if the API has one.",{"type":62,"tag":142,"props":363,"children":364},{},[365,370],{"type":62,"tag":67,"props":366,"children":367},{},[368],{"type":76,"value":369},"If it was modified, refetch and retry.",{"type":76,"value":371}," Get the latest version, reapply your change on top, and send it with the new version or ETag. Don't overwrite blindly.",{"type":62,"tag":142,"props":373,"children":374},{},[375,380],{"type":62,"tag":67,"props":376,"children":377},{},[378],{"type":76,"value":379},"If an idempotent request is in flight, wait",{"type":76,"value":381}," and retry with the same key. You'll get the original request's result.",{"type":62,"tag":131,"props":383,"children":385},{"id":384},"_409-and-duplicates-on-a-webhook-endpoint",[386],{"type":76,"value":387},"409 and duplicates on a webhook endpoint",{"type":62,"tag":63,"props":389,"children":390},{},[391,393,398,400,405],{"type":76,"value":392},"Webhooks are delivered ",{"type":62,"tag":309,"props":394,"children":395},{"href":30},[396],{"type":76,"value":397},"at least once",{"type":76,"value":399},". The same event can arrive twice when your endpoint processed it but timed out before answering, when a provider retries, or when someone replays it. Your handler needs to be ",{"type":62,"tag":309,"props":401,"children":402},{"href":27},[403],{"type":76,"value":404},"idempotent",{"type":76,"value":314},{"type":62,"tag":63,"props":407,"children":408},{},[409,411],{"type":76,"value":410},"The tempting response to a duplicate is 409. Don't. Most providers treat any non-2xx response as a failed delivery and retry it, so a 409 produces more duplicates, and it can push the endpoint toward being flagged as failing. ",{"type":62,"tag":67,"props":412,"children":413},{},[414,416,422],{"type":76,"value":415},"Answer a duplicate with ",{"type":62,"tag":71,"props":417,"children":419},{"className":418},[],[420],{"type":76,"value":421},"200",{"type":76,"value":423}," and skip the work.",{"type":62,"tag":63,"props":425,"children":426},{},[427],{"type":76,"value":428},"The standard pattern:",{"type":62,"tag":339,"props":430,"children":431},{},[432,469,474],{"type":62,"tag":142,"props":433,"children":434},{},[435,437,443,445,451,453,459,461,467],{"type":76,"value":436},"Take the provider's unique event or delivery ID (Stripe's ",{"type":62,"tag":71,"props":438,"children":440},{"className":439},[],[441],{"type":76,"value":442},"event.id",{"type":76,"value":444},", GitHub's ",{"type":62,"tag":71,"props":446,"children":448},{"className":447},[],[449],{"type":76,"value":450},"X-GitHub-Delivery",{"type":76,"value":452},", the ",{"type":62,"tag":71,"props":454,"children":456},{"className":455},[],[457],{"type":76,"value":458},"webhook-id",{"type":76,"value":460}," header in ",{"type":62,"tag":309,"props":462,"children":464},{"href":463},"/glossary/standard-webhooks/",[465],{"type":76,"value":466},"Standard Webhooks",{"type":76,"value":468},").",{"type":62,"tag":142,"props":470,"children":471},{},[472],{"type":76,"value":473},"Insert it into a table with a unique constraint, or a cache with a TTL, in the same transaction as the work.",{"type":62,"tag":142,"props":475,"children":476},{},[477,479,484],{"type":76,"value":478},"If the insert conflicts, it's a duplicate: return ",{"type":62,"tag":71,"props":480,"children":482},{"className":481},[],[483],{"type":76,"value":421},{"type":76,"value":485}," and stop.",{"type":62,"tag":63,"props":487,"children":488},{},[489,491,496,498,504,506,512,514,519],{"type":76,"value":490},"If you'd rather drop duplicates before they reach your app, a Webhook Relay function can do it with the built-in ",{"type":62,"tag":309,"props":492,"children":493},{"href":20},[494],{"type":76,"value":495},"KV store",{"type":76,"value":497},": if ",{"type":62,"tag":71,"props":499,"children":501},{"className":500},[],[502],{"type":76,"value":503},"kv.has(id)",{"type":76,"value":505}," is true, call ",{"type":62,"tag":71,"props":507,"children":509},{"className":508},[],[510],{"type":76,"value":511},"r.stopForwarding()",{"type":76,"value":513}," so the duplicate is logged but not delivered; otherwise store the ID with a TTL that covers the provider's retry window. KV operations aren't atomic, so two copies arriving at the exact same moment can both pass. Keep a unique constraint in your database as the final guard. The ",{"type":62,"tag":309,"props":515,"children":516},{"href":22},[517],{"type":76,"value":518},"retries and idempotency guide",{"type":76,"value":520}," covers the full design.",{"type":62,"tag":131,"props":522,"children":524},{"id":523},"when-to-return-409-from-your-own-api",[525],{"type":76,"value":526},"When to return 409 from your own API",{"type":62,"tag":63,"props":528,"children":529},{},[530,532,538,540,546,548,553],{"type":76,"value":531},"Return ",{"type":62,"tag":71,"props":533,"children":535},{"className":534},[],[536],{"type":76,"value":537},"409",{"type":76,"value":539}," when a request would succeed against a different server state: a unique field already in use, a stale version, an invalid state transition, or an idempotency key whose first request is still running. Include enough detail in the body (what conflicts, the existing ID, the current version) for the client to resolve it automatically. Use ",{"type":62,"tag":71,"props":541,"children":543},{"className":542},[],[544],{"type":76,"value":545},"412",{"type":76,"value":547}," if you implement ETags with ",{"type":62,"tag":71,"props":549,"children":551},{"className":550},[],[552],{"type":76,"value":280},{"type":76,"value":314},{"type":62,"tag":555,"props":556,"children":557},"style",{},[558],{"type":76,"value":559},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":7,"searchDepth":113,"depth":113,"links":561},[562,563,564,565,566],{"id":133,"depth":104,"text":136},{"id":210,"depth":104,"text":213},{"id":334,"depth":104,"text":337},{"id":384,"depth":104,"text":387},{"id":523,"depth":104,"text":526},"markdown","content:http-errors:409-conflict.md","content","http-errors/409-conflict.md","http-errors/409-conflict","md",{"loc":4},1793199801733]