Refund an Installment
Issue full or partial refunds for completed bank installment transactions. Amwal coordinates with the customer's issuing bank to adjust or cancel future installment billings.
Refund State Flow
A 200 response only means the refund was accepted — settlement
happens afterward and asynchronously. The diagram below covers request-time
validation, the P → R settlement path, the known issue with repeated
refunds on the same transaction, and the resulting status changes on both
the transaction and the payment link.
The 2nd+ refund path is a known, open issue
Repeated refunds on the same transaction have been observed getting stuck
at status: "P" indefinitely — see the "Settlement Is Asynchronous"
section further down this page for what to check for and how to work
around it in the meantime.
Endpoint
POST /transactions/refund/{transaction_id}/Trailing slash is required
The trailing slash after {transaction_id} is not optional. Omitting it
returns an HTTP redirect instead of processing the refund.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
transaction_id | String (UUID) | Yes | The unique Transaction UUID to refund (e.g. 60b43176-5991-4475-8ef6-c73c2417c805). |
Request Headers
Authorization: YOUR_SECRET_KEY
X-Amwal-Key: sandbox-amwal-xxx
Content-Type: application/json| Header | Type | Required | Description |
|---|---|---|---|
Authorization | String | Yes | Your Amwal Secret Key (e.g. d9ccf8bc-ed63-44ad-a54c-9d8fee63df6b). |
X-Amwal-Key | String | No | Merchant publishable API Key or sandbox identifier. |
Content-Type | String | Yes | Must be set to application/json. |
Request Body
{
"refund_amount": 100.00,
"reason": "Customer returned partial order items",
"metadata": {
"rma_number": "RMA-9842"
}
}Request Parameters
| Field | Type | Required | Description |
|---|---|---|---|
refund_amount | Number | Yes | Amount to refund in SAR. Rejected with 400 if it exceeds the transaction's remaining refundable balance. |
reason | String | No | Internal audit reason for the refund. |
metadata | Object | No | Optional merchant tracking metadata (e.g. rma_number). |
Response (200 OK)
{
"status": "success",
"key_type": "RefundReference",
"key": "2026001726"
}Response Parameters
| Field | Type | Description |
|---|---|---|
status | String | Outcome of the refund acceptance: "success". |
key_type | String | Reference identifier type: "RefundReference". |
key | String | Gateway-generated refund reference tracking number (e.g. "2026001726"). Use this reference for bank inquiry and audit tracking. |
key is the gateway's refund reference number. This response confirms the refund was accepted by the gateway. To inspect the resulting transaction state, remaining balance, and settlement progress, call
Get Payment Link Details or
Get Transaction Details. Once applied,
the transaction's status becomes partially refunded or refunded
(see Transaction Status), its
refunded_amount reflects the total refunded so far, and a refund_tracker
array is populated with one entry per refund (including the gateway
transaction id and RRN).
Settlement Is Asynchronous — a 200 Means "Accepted," Not "Settled"
A successful response only confirms the refund request was accepted.
The actual gateway settlement happens afterward, and you should confirm it
by reading the matching entry in refund_tracker:
refund_tracker[].status | Meaning |
|---|---|
P | Pending — the refund has been accepted but has not settled at the gateway yet. rrn is null at this stage. |
R | Settled — the refund has completed. rrn (the bank's retrieval reference number) is populated. |
refunded_amount can overstate what has actually settled
The transaction's top-level refunded_amount increases as soon as a
refund request is accepted, not once it settles — it sums every
accepted refund_amount, regardless of whether the corresponding
refund_tracker entry ever reaches status: "R". Do not treat
refunded_amount alone as proof that money has been returned to the
customer; check that the specific refund_tracker entry has settled.
Issuing more than one refund against the same transaction is where this
matters most. The first refund on a transaction settles normally. A
second (or third) refund attempted shortly after the first may be rejected
with 400 "The transaction is already under processing!" — retrying after
a short delay usually succeeds. However, that acceptance is not a
guarantee of settlement: repeated refunds on the same transaction have been
observed staying at status: "P" indefinitely (no rrn, and no
order.updated webhook ever fires for that entry), while
refunded_amount still reflects the full accepted total. If you need to
issue more than one refund on a transaction, poll
Get Payment Link Details and
verify each refund_tracker entry individually rather than relying on
refunded_amount or webhook delivery alone.
Errors
| Status | Body | Cause |
|---|---|---|
400 | {"message": "Maximum amount to be refunded is <amount>", "validation_errors": [...], "fields_errors": []} | refund_amount exceeds the transaction's remaining refundable balance. |
400 | {"message": "Your balance is not sufficient to initiate any refund process. You can retry when you have a balance exceeding the amount you are trying to refund.", "validation_errors": [{"Name": "Supplier", "Error": "Insufficient balance for supplier <id>"}], "fields_errors": []} | merchant/supplier account does not have enough available balance to cover this refund — a separate check from the transaction-level remaining-balance one above. Retry once your balance is sufficient. |
400 | {"message": "The transaction is already under processing!", "validation_errors": [{"Name": "Key", "Error": "The transaction is already under processing!"}], "fields_errors": []} | A previous refund on this same transaction hasn't finished processing yet. Transient — retrying after a short delay (a few seconds) usually succeeds. See the "Refund State Flow" diagram above and "Settlement Is Asynchronous" section below. |
403 | {"detail": "You are not allowed to refund this transaction"} | transaction_id does not exist, or does not belong to the authenticated merchant. Note this is 403, not 404. |
403 | {"detail": "This transaction is not refundable"} | The transaction is outside the refund eligibility window — see "Refund Time Window" below. Same response shape as the row above; check how old the transaction is to tell the two apart. |
Important Notes
| Topic | Detail |
|---|---|
| Sufficient balance | Your merchant account must have enough available balance to process a refund (see the "Insufficient balance for supplier" error above). |
| Online refunds | Typically take 3–5 business days to settle once accepted. |
| Bank installment refunds | Processing time varies, usually 5–7 business days. |
| Refund time window | Refunds are only accepted within 60–90 days of the original transaction. An older transaction returns 403 {"detail": "This transaction is not refundable"}. |
| Reference tracking | Keep the key value from the 200 response — it's the gateway's refund reference and is what customer service will need to look up the refund later. |
