ZyloPaydocs

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

EndpointIdempotency-Key
POST /v1/payment_intentsRequired (400 idempotency_key_required without it)
POST /v1/refundsRequired
Every other POST, PATCH and DELETEAccepted and recommended
GETNot 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 gets 409 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:

  1. 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), or 409 idempotency_request_in_progress.
  2. If it stays in progress, read the object: GET /v1/refunds?payment=pay_… or GET /v1/payment_intents?….
  3. 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}` });

On this page