Skip to main content
In this guide you’ll issue a refund against a payment you already collected, then track it to completion. You can refund the full amount or part of it. A refund returns money to the customer. You request it in the currency your payment settled in, and the customer is paid back in the currency they paid. Refunds are asynchronous: you create one, then a refund.* webhook tells you the outcome.

Before you start

  • A sandbox API key (sk_sandbox_...) with the refunds:write scope. See Permissions.
  • A payment to refund. Its charge_id comes from the payment or the collection.succeeded webhook.
  • A webhook endpoint to receive the result. See Set up webhooks.

What can be refunded

Check is_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, or underpaid. A payment that is still created or processing, or that already failed, 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 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.
The refund starts in 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
Pass a unique reference per refund (max 128 characters). Reusing one returns a duplicate error. Add an idempotency_key (max 255 characters) to make retries safe: the same key on the same payment returns the refund you already created, for 24 hours.
2

Track it with a webhook

Refunds finish asynchronously. Listen for the refund.* events to know the outcome.
refund.paid
You get 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.
If a create call fails with a network error (500, 502, 503, 504), do not assume the refund was not created. Retrieve it with the by-charge endpoint before you try again, or a blind retry answers 409 CONFLICT.

How long a refund takes

A refund passes through two stages, and only the first one is ours.
  1. With us. We reserve your balance and send the refund to the route that collected the payment. This is where refund.created reaches you.
  2. With the customer’s bank, wallet, or card issuer. We report refund.paid when 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.
Tell customers the second stage exists. A 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_bearer decides who absorbs any fee the refund itself carries: org (you) or customer (taken out of what the customer receives). If you omit it, your account’s fee handling decides. refund_fee_amount is "0" when no refund fee applies.

Crypto refunds

For a payment made in crypto, pass refund_address, the wallet the money goes back to, on the same network as the original payment. Without it the refund is refused.
A crypto refund cannot be recalled. Check the address with the customer before you create it.

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 with is_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 with simulated_outcome:
Values are success and failed. Sandbox refunds move no real money.

Next steps