Idempotent requests
Network failures, timeouts, and server errors happen in production. Idempotency keys let you retry failed requests safely without creating duplicate orders, charging customers twice, or sending multiple notifications for the same event. This guide explains how the Commerce API handles idempotency and how to implement retry logic that protects your customers and your business.
How it works
When you include an idempotency_key parameter in a request, Commerce stores successful responses associated with that key for 24 hours. If you retry a successful request with the same key within that window, Commerce returns the original response without re-executing the operation. This means your server can crash after initiating a payment but before recording the result, and when you retry with the same key, you'll get the original payment status—no duplicate charge.
Failed requests are not cached—if a request fails with validation errors, authentication issues, or server errors, you can immediately retry with the same idempotency key after addressing the underlying problem. This eliminates the burden of generating new keys for legitimate retries while still protecting against duplicate successful operations.
The idempotency guarantee applies per application—two different applications can use the same idempotency key without conflict. Keys are case-sensitive and must be between 1 and 255 characters. Commerce recommends constructing keys from stable business identifiers like order_${orderNumber} or payment_${cartId}_${timestamp} rather than random values—this makes debugging easier and ensures retries genuinely duplicate the original intent.
Important: Idempotency protects against duplicate execution of the same operation, but it doesn't protect against different operations that happen to use the same key. If you create an order with key order_123 for $100, then later try to create a different order with the same key for $200, Commerce returns the original $100 order—not an error. Always use keys that uniquely identify the specific operation you're attempting.
When to use idempotency keys
Use idempotency keys for any operation where duplicates would cause problems:
Critical operations (always use)
- Creating orders - Prevents charging customers twice for the same purchase
- Executing payments - Ensures payment attempts aren't duplicated during retries
- Scheduling payouts - Avoids sending funds multiple times to the same destination
- Sending notifications - Prevents customers from receiving duplicate order confirmations or payment receipts
When network failures occur
- Socket timeouts before receiving a response
- Connection drops mid-request
- HTTP 5xx server errors (500, 502, 503, 504)
- HTTP 429 rate limit errors (safe to retry with backoff)
How to use idempotency keys
Include an idempotency_key parameter in your request body. The key is a string between 1 and 255 characters that uniquely identifies the operation you're attempting. Here's the basic pattern across all supported languages:
Using idempotency keys
curl https://api.zebo.dev/orders/new \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"idempotency_key": "order_checkout_abc123_1737371200",
...
}'
The pattern here is to construct idempotency keys from stable business identifiers rather than random values. The checkout_id identifies the specific operation (which cart or session), while the timestamp ensures uniqueness if the same cart is checked out multiple times. If a request times out, retry with the exact same key to retrieve the original response.
Constructing effective keys
Good idempotency keys are predictable, debuggable, and tied to business events. They should contain enough context to understand what operation they represent when reading logs or investigating issues.
Recommended patterns:
order_${cart_id}_${timestamp}- Order creation from shopping cartpayment_${order_id}_attempt- Payment execution for an orderpayout_${batch_id}_${currency}- Scheduled payout for a batchnotify_${resource_type}_${resource_id}_${event}- Event-driven notificationsrefund_${payment_id}_${amount}- Partial or full refund operations
Key characteristics:
- Stable identifiers - Use business IDs (order numbers, cart IDs) not random UUIDs
- Event context - Include what happened (
confirmed,failed,shipped) - Resource type - Prefix with resource name (
order_,payment_,payout_) - Timestamp when needed - Add Unix timestamp for operations that can repeat legitimately
- Length constraint - Keep under 255 characters (Commerce enforces 1-255 character range)
Avoid these patterns:
- ❌
random_uuid()- Can't correlate to business event when debugging - ❌
request_${counter}- Counter state is hard to maintain across retries - ❌
${timestamp}_only- Doesn't identify what operation is being attempted - ❌
order- Not unique, will incorrectly return cached response
Retry behavior and timing
When you retry a successful request with an idempotency key, Commerce returns the original response exactly as it was first returned—same HTTP status, same response body, same headers. Failed requests are not cached, so you can retry immediately with the same key after fixing validation errors, authentication issues, or waiting out transient server problems.
What gets cached:
- Successful responses only - 2xx status codes with full response body are stored for 24 hours
- Failed requests are not cached - 4xx and 5xx errors can be retried immediately with the same key
Cache duration:
Commerce stores successful responses for 24 hours from the original request. After 24 hours, the same idempotency key can be reused for a genuinely new operation. This 24-hour window is sufficient for most use cases while allowing keys to naturally expire for legitimate new operations.
Retry strategy:
Implement exponential backoff when retrying failed requests—don't hammer the API with rapid retries. Start with a 1-second delay and double it after each failure:
- First retry: wait 1 second
- Second retry: wait 2 seconds
- Third retry: wait 4 seconds
- Fourth retry: wait 8 seconds
- Fifth retry: wait 16 seconds
- Give up or alert support after 5 attempts
This approach gives transient issues time to resolve while avoiding API rate limits. For HTTP 429 rate limit errors, respect the Retry-After header instead of using exponential backoff.
Idempotency key requirements
Commerce validates idempotency keys before processing requests to ensure they meet system constraints:
Length constraints:
- Minimum: 1 character
- Maximum: 255 characters
- Keys outside this range return a
400 Bad Requesterror
Format requirements:
- Case-sensitive -
Order_123andorder_123are different keys - No special encoding - Use plain ASCII or UTF-8, no URL encoding required
- Whitespace allowed - Leading/trailing spaces are trimmed automatically
Uniqueness scope:
- Per application - Different applications can use the same key without conflict
- Per endpoint - Same key can be used for different operations (order vs. payment)
- Time-bound - Keys expire after 24 hours and can be reused
If you provide an invalid idempotency key (too short, too long, or improper format), Commerce returns an error before attempting to process the request—no operation is executed, and you can immediately retry with a corrected key.
Related resources
- Accept a payment - Complete payment flow with idempotency
- Send customer notification - Notification delivery with duplicate protection
- Errors - Understanding error responses and retry strategies
- Authentication - Securing API requests with bearer tokens