Refunds
Return a settled payment, fully or partially, and track it to completion.
Refund a settled order back to the payer. Crypto has no chargeback mechanism — a refund is a
new outbound transaction you initiate, paid from your workspace balance back to the payer. You
create it, it processes on-chain, then a charge.refunded webhook fires. The accrued
fees on the original payment are reversed.
Before you start: refunds need the payments:write scope (Authentication),
and the original order must have reached PAID (see
Confirm the payment).
Only settled orders can be refunded
You can refund an order that reached PAID. The refund amount can be the full amount or less
(partial) — over-refunding beyond the collected total is rejected with a 422. Refunds are keyed
to the original merchantTradeNo.
Create a refund
const refund = await ap.refunds.create({
merchantTradeNo: "order-1024", // the settled order to refund
amount: { amount: "49.99", currency: "USDT" }, // ≤ the original amount (partial allowed)
reason: "customer request",
});
console.log(refund.refundRequestId, refund.status); // e.g. "rf_…", "PENDING"refund = ap.refunds.create(
merchant_trade_no="order-1024", # the settled order to refund
amount={"amount": "49.99", "currency": "USDT"}, # ≤ the original amount (partial allowed)
reason="customer request",
)
print(refund["refundRequestId"], refund["status"]) # e.g. "rf_…", "PENDING"refund, err := ap.Refunds.Create(ctx, absolutepay.RefundParams{
MerchantTradeNo: "order-1024", // the settled order to refund
Amount: absolutepay.Money{Amount: "49.99", Currency: "USDT"}, // ≤ the original amount (partial allowed)
Reason: "customer request",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(refund["refundRequestId"], refund["status"]) // e.g. "rf_…", "PENDING"BODY='{"merchantTradeNo":"order-1024","amount":{"amount":"49.99","currency":"USDT"},"reason":"customer request"}'
curl https://api.absolutepay.io/v1/refunds \
-H "Authorization: Bearer $APP_TOKEN" -H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
$(sign POST /v1/refunds "$BODY") -d "$BODY"Track it
const status = await ap.refunds.get(refund.refundRequestId);status = ap.refunds.get(refund["refundRequestId"])status, err := ap.Refunds.Get(ctx, refund["refundRequestId"].(string))
if err != nil {
log.Fatal(err)
}
fmt.Println(status)curl "https://api.absolutepay.io/v1/refunds/rf_…" \
-H "Authorization: Bearer $APP_TOKEN" $(sign GET /v1/refunds/rf_… "")A refund starts PENDING and reaches REFUND_SUCCESS (or REFUND_REJECTED) when it settles — at
which point the charge.refunded webhook fires. Treat that webhook as the source of truth and act
idempotently on the refund id. Full fields are on POST /v1/refunds in the API reference.
Gotchas
- A settled refund is final. It's an on-chain transfer — once
REFUND_SUCCESS, it cannot be reversed or recalled. Double-check the amount before creating one. - Retries:
POST /v1/refundsis a money-moving call — it accepts anIdempotency-Keyheader (shown in the curl tab); reuse the same key when retrying so the refund is applied at most once. After an ambiguous failure (timeout), check the refund's state before creating it again. - Amounts are decimal strings (
"49.99"), positive, ≤ 6 fraction digits, in the original payment's currency. Partial refunds are allowed up to the collected total across all refunds on the order. - In the sandbox, refunds are mock-backed — no real on-chain transfer happens, and the same
charge.refundedwebhook fires on completion, so you can test your handler end to end.
Next
- Accept payments — the checkout flow that produces refundable orders.
- Webhooks — handle
charge.refunded. - Balances & reconciliation — see the fee reversal in your ledger.
- API reference — full fields on
POST /v1/refunds.