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:

  1. First request -- Luqra Now creates the payment and returns 201 Created with the new payment ID. The key is recorded against the payment.

  2. Same key + same body -- Luqra Now returns the original 201 response (same payment ID). No duplicate payment is created.

  3. Same key + different body -- Luqra Now returns 409 Conflict with code IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_BODY. Mint a new key for the new request.

  4. 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; externalPaymentId is 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.