/refund
Initiate full or partial refunds on orders that were charged through FlexFactor.
Prerequisites to invoke this API:
- Authenticate and send the bearer token for this call.
Idempotency — full vs. partial refundsFull refunds are de-duplicated server-side via the order ID: repeated full-refund calls for the same order will not double-credit, with or without a key.
Partial refunds are not auto-deduplicated. Because multiple distinct partial refunds against one order are legitimate, the order-ID guard cannot tell a retry apart from an intentional second partial refund. Send an
idempotencyKey(a GUID, in the JSON body) on every partial refund and reuse it on retries — within the 24-hour replay window FlexFactor returns the original response instead of issuing a second partial credit, and a reused key carrying a different payload is rejected as a409conflict. See the Idempotency reference (§3.4) for the full replay-window, response-code, and retry behavior.Retrying a failed refund with the same key. A refund the processor declined is not stored against the key, so a retry with the same key runs the refund again. A refund that failed for an unexpected reason (
errorscontainsREFUND_FAILED) is stored, and a retry with the same key replays theFAILEDresponse without running the refund again, because the processor might already have credited the cardholder. Before issuing that refund again under a new key, confirm withGET /v1/orders/{id}thatrefundsAmountdoes not already include it.
Request
Details
| HTTP Method | POST |
| Endpoint - Sandbox | https://api-sandbox.flexfactor.io/v1/orders/{id}/refund |
| Endpoint - Production | https://api.flexfactor.io/v1/orders/{id}/refund |
{id} | FlexFactor unique transaction identifier.\ This is the FlexFactor orderId returned by /evaluate and/or GET /v1/orders/{id} and/or webhook and/or SFTP batch. |
Body
amount | Integer | Required — send amount or amountToRefund, not both | Amount to refund, in cents, for example 2500 for $25.00. If you send neither, the request fails validation with HTTP 400. |
amountToRefund | Decimal | Deprecated | Amount to refund, in dollars, for example 25.00. Still accepted for existing integrations; use amount in new integrations. |
refundMessage | String | Optional | Additional message you want to send with the request, for example "Customer request". |
idempotencyKey | Guid | Optional — recommended for partial refunds | Full refunds are de-duplicated server-side by order ID; partial refunds are not. Send a GUID here and reuse it on retries to prevent duplicate partial refunds — it replays the original response within the 24h window and detects reused-key conflicts. See Idempotency. |
isConsumerRequestedRefund | Boolean | Optional | true when the cardholder requested the refund. |
transactionId | Guid | Optional | FlexFactor identifier of the transaction to refund. |
supportedGatewayId | Guid | Optional | FlexFactor identifier of the gateway that processes the refund. |
Full and partial refunds
Full and partial refunds use the same request. amount is the amount to return to the cardholder, in cents, which matches /capture and /void. An amount equal to the captured amount is a full refund; a lower amount is a partial refund. You can issue several partial refunds against one order.
- There is no
currencyfield. The refund is applied in the currency of the original order. - Send an
idempotencyKeyon every partial refund and reuse it on retries (see the callout above). Full refunds are de-duplicated by order ID. - After a refund, the order stays
completed— a refund does not move it toreturned.GET /v1/orders/{id}reports the refunded total inrefundsAmount(dollars), and theorder.refundedwebhook fires withEventData.Amountin cents. See the state model in Workflows §3.5.
If the refund amount exceeds the remaining refundable balance, the request fails validation and returns HTTP 400.
Migrating fromamountToRefund
amountToRefund(dollars) is deprecated but still accepted. To migrate, multiply by 100 and send the result asamount:amountToRefund: 25.00becomesamount: 2500. Send one field or the other, not both.GET /v1/orders/{id}still reportsrefundsAmountin dollars.
Try it right now:
- Check out the /refund Reference.
- Check out our Postman Collection
Response
The refund response uses the common FlexFactor envelope, but on this endpoint status carries the refund outcome (SUCCESS or FAILED) rather than a transaction status such as APPROVED. Read success and status; result and statusCode are never populated on this endpoint and are always null. The envelope and the transaction state model are defined in Workflows §3.5.
status | string | Refund outcome: SUCCESS | FAILED. Key your refund logic on this field. |
success | boolean | true when errors is empty, that is, when the refund succeeded. |
result | string | Always null on this endpoint. Do not use it. |
responseCode | string | Processor response code when the refund failed. null on success. Optional |
responseMessage | string | Processor response message when the refund failed. null on success. Optional |
transactionId | guid | Identifier of the refund transaction. Set on success; null on failure. |
statusCode | string | Always null on this endpoint. It is not the HTTP status code. Do not use it. |
errors | array of objects | Error details for a failed refund; empty on success. Each item is serialized positionally: item1 message, item2 key, item3 error code, item4 friendly flag (boolean). An unexpected failure carries REFUND_FAILED. |
customProperties | object | Additional custom properties for the API request Optional |
HTTP status codes
A failed refund normally returns HTTP200A refund the processor declined, and a refund that failed for an unexpected reason, both return HTTP
200withstatus: FAILEDandsuccess: false, so that you can inspect the body and retry if appropriate. Checksuccessandstatuson every200; do not treat the HTTP code alone as the outcome.
| Situation | HTTP | Body |
|---|---|---|
| Refund succeeded | 200 | status: SUCCESS, success: true |
| Processor declined or returned a failure | 200 | status: FAILED, success: false, responseCode, responseMessage, errors |
| Unexpected error (for example a gateway or transport failure) | 200 | status: FAILED, success: false, errors contains REFUND_FAILED |
Validation error (for example the refund amount exceeds the remaining refundable balance, or neither amount nor amountToRefund was sent) | 400 | Validation problem details — see Error Handling |
| Invalid or expired bearer token | 401 | — |
| Error raised before the refund ran | 400 | Refund envelope with status: FAILED |
No order matches {id}, or it belongs to another merchant | 404 | Problem details |
Same idempotencyKey reused with a different body, or the first request is still running | 409 | Problem details — see Idempotency §3.4 |
An earlier attempt with the same idempotencyKey failed with an unknown outcome | 500 | Problem details |
| Duplicate-request check temporarily unavailable | 503 | Problem details, with a Retry-After header; safe to retry |
Updating the status orderAfter receiving a successful response from /refund, you need to update this order in your CRM with the corresponding status.
Payload examples
Request
curl --request POST \
--url https://api-sandbox.flexfactor.io/v1/orders/{id}/refund \ //insert the orderId returned by the response to /evaluate
--header 'Authorization: Bearer abc123def456' \ //replace with bearer returned by /oauth2
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"amount": 9900, //in cents — equal to the captured amount
"refundMessage": "Customer request"
}curl --request POST \
--url https://api-sandbox.flexfactor.io/v1/orders/{id}/refund \ //insert the orderId returned by the response to /evaluate
--header 'Authorization: Bearer abc123def456' \ //replace with bearer returned by /oauth2
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"amount": 2500, //in cents — lower than the captured amount
"refundMessage": "Partial return",
"idempotencyKey": "{generate_a_GUID_and_reuse_it_on_retries}"
}Response
{
"status": "SUCCESS",
"success": true,
"result": null,
"responseCode": null,
"responseMessage": null,
"transactionId": "{{$guid}}",
"statusCode": null,
"errors": [],
"customProperties": {}
}{
"status": "FAILED",
"success": false,
"result": null,
"responseCode": "{processor response code}",
"responseMessage": "{processor response message}",
"transactionId": null,
"statusCode": null,
"errors": [
{ "item1": "{failure reason}", "item2": "{error key}", "item3": "{error code}", "item4": true }
],
"customProperties": {}
}Updated 6 days ago

