Idempotency
Retry any POST safely with an Idempotency-Key. Payments and refunds require one.
Networks fail. A request can time out after ZyloPay has done the work. An idempotency key makes a retry safe: ZyloPay runs the request once and replays its response to every retry with the same key.
curl https://zylopay-api.fly.dev/api/v1/refunds \
-H "Authorization: Bearer $ZYLOPAY_SECRET_KEY" \
-H "Idempotency-Key: refund-R-2211" \
-H "Content-Type: application/json" \
-d '{"payment": "pay_1042", "amount": 1000000}'Where it applies
| Endpoint | Idempotency-Key |
|---|---|
POST /v1/payment_intents | Required (400 idempotency_key_required without it) |
POST /v1/refunds | Required |
Every other POST, PATCH and DELETE | Accepted and recommended |
GET | Not needed: reads never change anything |
A key is 1-255 printable ASCII characters (400 idempotency_key_invalid otherwise).
How it behaves
- First request: ZyloPay claims the key, runs the request and stores its response, success or error, for 48 hours.
- Same key, same request: the stored response is replayed, with the header
Idempotent-Replayed: true. Nothing runs again. - Same key, different request (another method, path or body):
409 idempotency_key_reused. Nothing runs. - Same key while the first is still running:
409 idempotency_request_in_progress. Wait and retry with the same key. - A request refused by validation (
parameter_invalid,parameter_missing,parameter_unknown) frees the key, so you can fix the body and reuse it. - A request that timed out (
503 request_timeout) keeps running; its real response is stored when it finishes, and a retry with the same key then replays it. Until then the retry gets409 idempotency_request_in_progress. - A request interrupted by a crash stays "in progress" until the key expires. ZyloPay never runs a payment or a refund twice on a guess. Check the object before doing anything else.
Keys are scoped to your account and mode, not to one API key: after a key rotation, the new API key sees the same idempotency keys.
Choosing keys
Use a key that identifies your operation, not the attempt:
intent-<your order ID>-<attempt>for a payment intent;refund-<your return ID>for a refund.
A key derived from your own record survives a crash of your process. A random UUID generated at send time is safe against network retries, but a restarted process would generate a new one and could create a second intent.
Timeouts and processing
POST /v1/refunds waits up to 60 seconds for the transaction receipt. Other routes answer within 30 seconds. When a route runs out of time it answers 503 request_timeout; the work may still complete.
After a timeout or a 5xx:
- Retry with the same key. You get the stored response (after a timeout, the real outcome once it finished; after a
5xx, the same error), or409 idempotency_request_in_progress. - If it stays in progress, read the object:
GET /v1/refunds?payment=pay_…orGET /v1/payment_intents?…. - Never retry a money movement with a new key until you know the first one failed.
A pending refund or a processing payment intent is not a failure. ZyloPay resolves it and sends a webhook.
In the Node SDK
@zylopay/node generates an idempotency key for every POST and reuses it across its own automatic retries. Pass your own key when your application may re-submit the same operation later, for example after a crash:
await zp.refunds.create({ payment: 'pay_1042', amount: 1000000 }, { idempotencyKey: `refund-${returnId}` });