Idempotency and error handling
Certification cases for idempotency keys, timeouts, order recovery, error responses, and response handling rules.
Part of the Certification guide. Mark your scope on that page before you run these cases. Base payloads and test cards are on the Reference page.
11. Idempotency, timeouts, and order recovery
Applies to: CORE. idempotencyKey is a GUID in the JSON body and is required on /evaluate and /capture. A completed response is replayed for 24 hours. Persist the key before you send the request, so a crash does not lose it. Generate a new key for a merchant orderId only after GET /v1/orders?query={merchant orderId} confirms that no order exists.
Reference: Workflows §3, Idempotency reference, GET /orders/{id}, GET /orders, Appendix C.2.
| ID | Scenario | Setup / action | Expected result | Evidence |
|---|---|---|---|---|
| IDEMPOTENCY-01 | Replay within 24 hours | Send the base CIT payload with key K1; send it again with K1 | Both responses are identical; GET /v1/orders?query={merchant orderId} returns exactly 1 order | Both responses, order count |
| IDEMPOTENCY-02 | Same key, different payload | Send K1 again with a different transaction.amount | HTTP 409; no new order | traceId |
| IDEMPOTENCY-03 | Concurrent duplicate | Send two identical requests with key K2 at the same time | One returns HTTP 200; the other returns HTTP 409 with the message request already in progress, retry shortly; 1 order exists | Both responses |
| IDEMPOTENCY-04 | Key missing or not a GUID | Base CIT payload with idempotencyKey omitted, then with idempotencyKey order-123 | HTTP 400 both times with an errors object naming idempotencyKey | traceId |
| IDEMPOTENCY-05 | Client timeout, then retry with the same key | Set your client read timeout below the sandbox response time (for example 100 ms), send the base CIT payload with key K3, observe the timeout, restore the timeout, and resend with K3 | The resend returns HTTP 200 with the outcome of the first request; 1 order exists | Response, order count |
| IDEMPOTENCY-06 | Recover the orderId by external order ID | After IDEMPOTENCY-05, GET /v1/orders?query={merchant orderId} | The matching order is returned with its FlexFactor orderId, status, and statusName | Response |
| IDEMPOTENCY-07 | Order inquiry by FlexFactor orderId | GET /v1/orders/{orderId} for an order from Section 3 or Section 5 | HTTP 200; numeric status and statusName match the table in Appendix C.2 | Response |
| IDEMPOTENCY-08 | Inquiry for an unknown order | GET /v1/orders/{random GUID} | HTTP 404 | HTTP status |
12. Error handling
Applies to: CORE. Section 12.1 holds the error responses the sandbox can produce on request. Section 12.2 lists the responses the sandbox cannot force and the handling each one requires.
Reference: Error handling, Workflows §5, Appendix C.4.
12.1 Error responses
| ID | Scenario | Setup / action | Expected result | Evidence |
|---|---|---|---|---|
| ERROR-01 | Validation error shape | Base CIT payload with transaction.amount 0 | HTTP 400; body has status, title, traceId, and an errors object keyed transaction.amount | traceId |
| ERROR-02 | Wrong MID | Base CIT payload with a MID that does not belong to your credentials | HTTP 403 or 400; no order created | traceId |
12.2 Response handling rules
The sandbox cannot force these responses. For each rule, state in one sentence how your integration behaves when it receives the response. The Solutions engineer accepts the written statement as evidence; no logs or code excerpts are required.
| ID | Response | Required handling |
|---|---|---|
| HANDLE-01 | Any HTTP 200 | The decision keys on status. result is read on /evaluate only. HTTP 200 alone never marks an order paid, captured, refunded, or cancelled |
| HANDLE-02 | /evaluate HTTP 200 with result Failed, success false, and status DECLINED | The evaluation did not complete. The merchant receives the original decline. No FlexFactor decision is recorded, and the request is not retried with a new idempotencyKey |
| HANDLE-03 | /capture HTTP 200 with status FAILED and success true | The funds were not captured. The order is not marked paid and stays Capturerequired until the capture window elapses |
| HANDLE-04 | /refund HTTP 200 with status FAILED and success false | The refund is not marked complete. A processor decline (responseCode and responseMessage populated) is retried under the same key. An unexpected failure (REFUND_FAILED in errors) is not retried under a new key until GET /v1/orders/{id} confirms that refundsAmount does not already include it |
| HANDLE-05 | /cancel HTTP 200 | The order is marked cancelled only after GET /v1/orders/{id} returns status 2 or order.cancelled arrives |
| HANDLE-06 | HTTP 400, or HTTP 409 from a reused key | The request is not retried. The traceId is logged and the merchant receives the original decline. A reused key is a client defect and raises an alert |
| HANDLE-07 | HTTP 409 with request already in progress, retry shortly | The same request and key are resent after a backoff of at least 2 seconds |
| HANDLE-08 | HTTP 500, 502, or 504 | The same request and key are retried with exponential backoff, at most 3 attempts. When a 500 repeats on the same key, automatic retries stop, an alert is raised, and the order is resolved through GET /v1/orders |
| HANDLE-09 | HTTP 503 | When the response carries Retry-After, the same request and key are resent after the header value. Otherwise the merchant receives the original decline and the request is not retried |
| HANDLE-10 | No response (connection failure or client timeout) | The merchant receives the original decline within the merchant decision window. The order is recovered as in IDEMPOTENCY-05 and IDEMPOTENCY-06, and a new key is generated only when no order exists |
Updated about 8 hours ago

