Track a refund
Its webhooks, why a refund fails, a failing refund in sandbox, and listing refunds.
Webhooks
Six events follow a refund. Subscribe to each by name: a subscription matches exact names only, and
refund.* matches nothing.
| Event | Fires when |
|---|---|
refund.created | The refund was accepted and recorded |
refund.processing | The refund reached the provider. Live only |
refund.succeeded | Funds returned. This is the one to reconcile on |
refund.failed | The provider refused or could not complete it, read failure_reason |
refund.cancelled | Wajub stopped a live refund before sending it, with cancelled_by_wajub: nothing left your balance. Sent once API version 2026-10-12 is current, as refund.failed until then |
refund.updated | A live refund changed lane: execution_mode or manual_reason changed. Live only |
refund.processing never fires in sandbox
In live, a refund moves from pending to processing when it reaches the provider, and
refund.processing is sent. In sandbox a refund is created processing directly, so that event
never comes. Use refund.created if you need a signal at the start in both environments.
refund.succeededeventDeduplicate on X-Wajub-Delivery-Id as you do everywhere else. Registration and signature
verification are in Webhooks.
Test a failing refund in sandbox
Sandbox outcomes are deterministic, and they are decided by the payment method of the original payment, not by anything you send on the refund. Pay with the right number, then refund it, and you get the outcome you wanted.
For a payment made with Mobile Money, the last six digits of the phone number decide.
| Suffix on the original payment | Refund outcome |
|---|---|
000000 | Succeeds |
000009 | Payment succeeds, refund always fails. The one to test against |
000001, 000002, 000003, 000004 | Nothing to refund: the payment itself never succeeds, so the refund call answers 422 |
| Anything else | Succeeds |
For a card payment, 4000000000005423 charges normally and then fails every refund. Every other
test card refunds cleanly. Crypto and bank payments always refund successfully, since there is no
failure address to test with.
A failed sandbox refund carries a failure_reason drawn at random from insufficient_funds,
refund_rejected_by_issuer, transaction_already_reversed, network_error, provider_error and
provider_declined, with a readable failure_message beside it. These are codes a live refund uses
too, listed under Why a refund failed.
The one case that is random
When the original payment has no payment method attached at all, sandbox falls back to a coin toss weighted to succeed roughly seven times in ten. If your tests flake, that is why: pay with a real test number so the outcome is chosen rather than rolled.
Why a refund failed
A failed refund always carries a failure_reason, from this list in production as in sandbox,
and a failure_message beside it: the provider's own words when it sent some.
failure_reason | What it means |
|---|---|
insufficient_funds | The provider's float could not cover the refund |
refund_rejected_by_issuer | A card issuer refused it |
transaction_already_reversed | The payment was already reversed or refunded at the provider |
invalid_number | The payer's number or account cannot receive it, for instance closed or not a subscriber. Live only |
limit_exceeded | A limit on the receiving account or at the provider was reached. Live only |
network_error | The provider could not be reached in time |
provider_error | The provider was down, or refused the call itself |
provider_declined | The provider refused it without saying why |
ops_failed | Wajub support could not complete a refund it had taken over, for no known reason: one the provider had refused keeps the provider's reason from this list. failure_message carries their note. Live only |
A failed refund takes nothing from your balance, with one exception in sandbox: a refund paying back a dispute you accepted, which stands for the dispute's loss (see The money moves on success). A retry is a new refund.
List refunds
Refunds come back newest first, across all your payments.
https://api.wajub.com/refunds| Query parameter | Accepts |
|---|---|
per_page | 1 to 100, defaults to 25 |
status | pending, processing, succeeded, failed, cancelled. Before API version 2026-10-12, a cancelled refund reads failed and failed lists it |
search | Free text over the refund reference and id |
date_from, date_to | Dates, date_to no earlier than date_from |
cursor | Switches to cursor pagination, for exports |
Retrieving a single refund takes its id.
https://api.wajub.com/refunds/{id}