Idempotency and Retries
Network failures, timeouts, and server errors are inevitable. Luqra Now uses an explicit Idempotency-Key HTTP header so you can safely retry requests without creating duplicate payments.
Using the Idempotency-Key header
Send a unique Idempotency-Key header on every POST /v1/payments request. The header is required.
curl -X POST https://staging.api.now.luqra.com/v1/payments/ \
-H "Authorization: Bearer luqra-now.org.test.YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7c2a4f14-9b6e-4e6f-9a1f-3d7e8e2b1c4d" \
-d '{
"originatorId": "ORIGINATOR_ID",
"contactId": "CONTACT_ID",
"paymentAmount": 5000,
"direction": "OUTBOUND",
"externalPaymentId": "inv-2026-001234"
}'
The key is a free-form string from 1 to 255 characters. A random UUIDv4 is the recommended shape.
How it works
When you send a payment creation request with an Idempotency-Key:
-
First request -- Luqra Now creates the payment and returns
201 Createdwith the new payment ID. The key is recorded against the payment. -
Same key + same body -- Luqra Now returns the original
201response (same payment ID). No duplicate payment is created. -
Same key + different body -- Luqra Now returns
409 Conflictwith codeIDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_BODY. Mint a new key for the new request. -
Failed request (any non-201 response) -- The key is not reserved. You can retry with the same key. Once a request succeeds (
201), the key locks in.
The body comparison ignores fields the API does not recognize. Reordering JSON keys or adding extra fields will not cause a mismatch.
There is no TTL. Once a key is reserved by a successful request, the same key always replays the original response.
externalPaymentId is a separate concept
externalPaymentId is your free-form business identifier for the payment (e.g., an invoice number). It is optional, may repeat across payments, and is not used for idempotency. If you send two payments with the same externalPaymentId under different Idempotency-Key values, you get two distinct payments tied together by your label in the response.
Retry strategy
How you retry depends on the response:
5xx errors and timeouts (no response received)
Retry with exponential backoff using the same Idempotency-Key. The original request either failed before reserving the key (so the retry creates the payment) or succeeded but the response was lost (so the retry replays the original 201). Either way, no duplicate.
Attempt 1: immediate
Attempt 2: wait 1 second
Attempt 3: wait 2 seconds
Attempt 4: wait 4 seconds
Attempt 5: wait 8 seconds
(give up after 5 attempts)
4xx errors (client errors)
Fix the request and retry. Failed requests do not reserve the key, so you can keep the same Idempotency-Key while adjusting the body.
| Code | Action |
|---|---|
400 IDEMPOTENCY_KEY_REQUIRED |
Add the Idempotency-Key header |
400 VALIDATION_ERROR |
Fix the request body or parameters |
401 |
Check your API key |
404 |
Verify the resource ID exists |
409 IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_BODY |
The same key was used previously with a different body. Mint a new key. |
After a successful 201
Once you receive a 201, the key is locked in. To create a new payment for the same logical thing -- for example, after a previous payment failed terminally -- mint a fresh Idempotency-Key. The same externalPaymentId may appear on both payments.
Which operations are safe to retry?
| Operation | Safe to retry? | Notes |
|---|---|---|
Create payment (with Idempotency-Key) |
Yes | Returns the original payment on replay; rejects body mismatch with 409 |
| Create contact | Yes | Returns 409 if the same contact already exists; no duplicate is created |
| Update contact | Yes | Applies the same patch; no cumulative side effects |
| Get payment | Yes | Read-only, always safe |
| List payments | Yes | Read-only, always safe |
| List contacts | Yes | Read-only, always safe |
| List originators | Yes | Read-only, always safe |
Best practices
- Generate a fresh UUID per logical request. A new UUIDv4 per payment attempt is the simplest, most robust approach.
- Persist the key client-side before sending. If your process crashes after sending the request but before recording the response, retrying with the persisted key recovers the original payment.
- Do not derive the key from
externalPaymentId. They serve different purposes -- the key is per-attempt;externalPaymentIdis your stable business reference. - Log the response payment ID. Saves a lookup if your retry hits an already-reserved key.
- Set reasonable timeouts. A 30-second timeout is a good starting point. On timeout, retry with the same key.