refund.* webhook tells you the outcome.
Before you start
- A sandbox API key (
sk_sandbox_...) with therefunds:writescope. See Permissions. - A payment to refund. Its
charge_idcomes from the payment or thecollection.succeededwebhook. - A webhook endpoint to receive the result. See Set up webhooks.
What can be refunded
Checkis_refundable on the payment. When it is false, the refund is refused, so there is no point in trying. When it is true, the refund is accepted in almost every case, with one exception noted below.
A payment can be refunded when all of this is true:
- Its status is
succeeded,accepted, orunderpaid. A payment that is stillcreatedorprocessing, or that alreadyfailed, cannot be refunded. - Its payment method supports refunds. See the table below.
- It does not already carry a refund. A payment takes one refund. Once you refund part of a payment, you cannot come back later for the rest.
A refund that failed does not count. Nothing moved, so the payment becomes refundable again and you can create a new refund for it.
Supported refund currencies
Steps
1
Create the refund
Call The refund starts in
POST /v1/refunds with the payment you’re refunding and a unique reference. Omit amount for a full refund, or pass a decimal string for a partial one.processing, and your balance is reserved for it straight away. When a refund succeeds, the payment becomes refunded for a full refund or partially_refunded for a partial refund, and is_refundable becomes false. When we refund a payment automatically, it becomes auto_refunded instead, so you can tell the two apart.For a partial refund, add an amount in the payment’s settlement currency:Partial refund
2
Track it with a webhook
Refunds finish asynchronously. Listen for the You get
refund.* events to know the outcome.refund.paid
refund.created when the refund is accepted, refund.paid when it goes through, and refund.failed if it does not.3
Check the status any time
Retrieve a refund by ID to see where it is, or list refunds for reconciliation.
How long a refund takes
A refund passes through two stages, and only the first one is ours.- With us. We reserve your balance and send the refund to the route that collected the payment. This is where
refund.createdreaches you. - With the customer’s bank, wallet, or card issuer. We report
refund.paidwhen the money has left us. How long it then takes to appear in the customer’s account is set by their bank, not by us.
refund.paid webhook is not the same as money the customer can see.
These windows start from
refund.paid, not from the moment you created the refund. They are what the banks and wallets take, so quote them as a range and not as a promise.
What a refund costs you
- You request the amount in the settlement currency, the currency the payment paid you in. The customer gets back the currency they paid, converted at the rate the original payment settled at.
- If the payment converted currencies, you fund the conversion at today’s rate. Buying back the customer’s currency can cost more or less than you were paid, and the difference is yours. If we sold you a guaranteed rate on the payment, that rate stands and the movement is ours.
- Fees already charged are not returned. Our processing fee on the original payment stays charged, and on Connect the platform fee is not reversed either. See Refunds on Connect charges.
fee_bearerdecides who absorbs any fee the refund itself carries:org(you) orcustomer(taken out of what the customer receives). If you omit it, your account’s fee handling decides.refund_fee_amountis"0"when no refund fee applies.
Crypto refunds
For a payment made in crypto, passrefund_address, the wallet the money goes back to, on the same network as the original payment. Without it the refund is refused.
When a refund is refused
From the dashboard
You can refund without writing code. Open Transactions, click the payment, and choose Issue a refund. The button is offered on any payment withis_refundable: true, with the same one exception as the API. It appears in the same list and fires the same webhooks.
Testing
In the sandbox, force an outcome withsimulated_outcome:
success and failed. Sandbox refunds move no real money.
Next steps
- The payment object: find a payment’s
charge_idandis_refundable. - The refund object: every field on a refund.
- Refunds on Connect charges: which balance a refund debits on a split payment.
- Set up webhooks: receive
refund.*events.

