Idempotency keys on create endpoints
Problem
There is no idempotency support on any create endpoint. No handler reads an Idempotency-Key header, and the SDK's per-request idempotencyKey option sends nothing because the Stainless config declares no idempotency_header. Merchants who test for it find the option silently does nothing.
The only idempotency that exists today is a body field named idempotency_key on three endpoints: webhook create, credit-entitlement ledger entry, and wallet ledger entry. Nothing else has it.
Why it matters
A request timeout is normal client behaviour, and a retry after one is what every well-built integration does. Today that retry is indistinguishable from a new command, and on some endpoints it duplicates real money movement.
Checkout creation. If a merchant's server loses the session_id after we create the session, there is no way to recover it or prove it is unusable. There is no lookup by a merchant reference, metadata is not queryable, there is no list endpoint for sessions, and there is no expire or invalidate operation. Their only option is to create another session and let the orphan lapse.
Plan change. Tested in test mode: two identical POST /subscriptions/{id}/change-plan calls sent back to back both produced a charge, of INR 272,988 and INR 273,466. The amounts differ because both ran the pricing path independently, so there was no serialisation at all. A guard does exist and returns 409 once a payment is in flight, but it does not engage on concurrent or rapid duplicates.
Payment-method-update link. On an active subscription there is no guard. Every call mints a fresh link and a fresh payment row, so a double submit leaves two live links.
Requested
An Idempotency-Key header honoured across create endpoints, with the usual semantics: replaying the same key returns the original resource, reusing a key with different input is rejected, and the key is scoped per business and environment with a documented retention window.
Priority order by blast radius:
POST /subscriptions/{id}/change-plan— duplicates a real chargePOST /checkouts— unrecoverable lost sessionPOST /subscriptions/{id}/update-payment-method— duplicate linksPOST /payments,POST /subscriptions,POST /refunds
Also worth fixing alongside: the SDK exposes an idempotencyKey option that has no effect. Either wire it to a real header or remove it, because right now it actively misleads integrators into thinking they are protected.
Interim guidance being given to merchants
Gate retries on their own side, and key fulfilment on the returned payment_id rather than on anything they sent. That works but it pushes our race conditions onto them.
0 Comments
Sign in to comment
No comments yet. Be the first to share your thoughts!
