Any POST, PUT, or DELETE that mutates money (payments, refunds, transfers, webhook endpoint create/update) accepts an Idempotency-Key header.
How it works
- Generate a unique key per logical operation — a UUIDv4 is ideal.
- Send it as
Idempotency-Key: <value>. - QashX stores the SHA-256 of the request body against the key for 24 hours.
- Retries with the same key + same body return the original response — no side effects.
- Same key + different body returns
409 idempotency_conflict.
Example
curl -X POST https://ecmxmvrimionqhpbjguc.supabase.co/functions/v1/api-v1-payments \
-H "Authorization: Bearer $QASHX_API_KEY" \
-H "Idempotency-Key: 7c3a1d2f-9b8e-4d5a-a1c2-4e6f8091b234" \
-H "Content-Type: application/json" \
-d '{
"amount_minor": 4999,
"currency": "EUR",
"method_kind": "card",
"payer": { "email": "buyer@example.com" }
}'
Retrying this exact request within 24 hours returns the same payment.id, same status, no duplicate charge.
Best practices
- One key per intent — tie the key to your order/invoice, not the retry attempt. E.g.
ord_1042_charge_v1. - Never mutate the body between retries — even reordering optional fields is fine (canonical JSON is normalized), but a different
amount_minortriggers 409. - Persist the key before the first call — write to your DB, then call the API. If the process crashes between calls, you'll still have the same key on restart.
- Rotate keys per real change — if the customer edits the cart and you want a new charge, use a new key.
- UUIDs, not sequential IDs — avoid collision across environments and services.
Scope & TTL
| Aspect | Value |
|---|---|
| Scope | Per API key (test and live are separate). |
| Retention | 24 hours from first request. |
| Max key length | 255 characters. |
| Body match | SHA-256 of canonicalized JSON body. |
| Applies to | All mutating public API endpoints. |
Idempotency composes with rate limit retries — the safest client always sends both Idempotency-Key and honors Retry-After.