Idempotent requests
A network can fail after we have done what you asked but before you hear back. An idempotency key makes it safe to send the same request again: the second time, you get the first answer, and nothing is done twice. One payment is created however many times you retry.
Send a key of your choice in the Idempotency-Key header of every request that creates or changes something. POST /payments requires one: a request without it is 400 parameter_missing, with param Idempotency-Key.
curl https://api.63-180-91-99.sslip.io/payments \ -H "Authorization: Bearer sk_live_in_…" \ -H "Idempotency-Key: 5d1f6a0e-3c2b-4f8e-9a77-0c1d2e3f4a5b" \ -d …Choosing a key
Section titled “Choosing a key”- Make one key per operation, not per attempt: a retry of the same operation sends the same key.
- Use something that will not repeat, such as a UUID v4, or an id from your own system that names the operation (your order’s id and the step).
- A key is at most 255 characters, printable ASCII (a UUID is): any other is
400 parameter_invalid. - A key belongs to your account and your key’s mode: the same key from another account is another request. Within your account it names one request: sent to another endpoint, it is refused like another body.
What a retry gets
Section titled “What a retry gets”| You send | You get |
|---|---|
| The same key and the same request, after the first finished | The first request’s response, byte for byte and with its status (201 for POST /payments), with the header Idempotent-Replayed: true; nothing runs a second time. The payment is as it was then: read it with GET /payments/{id} for now |
| The same key while the first request is still running | 409 idempotency_key_in_use: try again shortly |
| The same key with another body, or to another endpoint | 400 with type idempotency_error and no code: a key names one request |
| A key older than 24 hours | A new request: keys are kept for 24 hours |
A request refused for its key, such as one with another body, does not use the key up. Neither does one refused for its parameters (400): correct it and send it with the same key.
GET requests read and change nothing, so they need no key; they are safe to retry as they are.
POST /settlements requires a key too, and keeps it its own way. A key there belongs to your merchant on the market, across all its accounts and their live keys (a settlement is your merchant’s, as your balance is), and is kept for good, not 24 hours. A retry with it answers the settlement as it is now, 201 with Idempotent-Replayed: true, and reserves nothing more: it may be paid or canceled by then. A retry while the first is still running waits for it and gets the same answer. The same key with another amount, payout_address or description is 400 idempotency_error; a key you used for a payment is another key there.
POST /payments/{id}/confirmations needs no key either: the same value sent again on the same payment records nothing new and answers the payment as it is now, so a retry is safe as it is. A key sent with it is not used.
POST /events/{id}/retry takes no key: each request asks for one more attempt of the event’s delivery, so a repeat sends the event again, as a resend should. A key sent with it is not used.
Where we differ from Stripe
Section titled “Where we differ from Stripe”Stripe’s API v1 keeps the result of a first request even when it failed on Stripe’s side, a 500 included, and answers every retry with that failure. We keep only a request that was carried out, as Stripe’s API v2 does not repeat a failure either. A payment and its key are written in one database transaction: when a request fails (a fault of ours, 503 temporarily_unavailable, or a refusal that depends on the moment, such as a payment that cannot change yet), nothing is written, neither the payment nor the key, and a retry with the same key runs the request again and can succeed. It can never make a second payment: the key is written with the first one. Our errors say which failures are worth retrying: 409, 429 and 5xx.