Skip to content

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.

EventFires when
refund.createdThe refund was accepted and recorded
refund.processingThe refund reached the provider. Live only
refund.succeededFunds returned. This is the one to reconcile on
refund.failedThe provider refused or could not complete it, read failure_reason
refund.cancelledWajub 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.updatedA 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.succeededevent
A full or partial refund has completed on the payment.
Payload
{
"id": "evt_Lm2Xc94Rf8Qp7Tz3Wk5Yb1Nd",
"event": "refund.succeeded",
"created": "2026-09-11T11:02:41+00:00",
"livemode": true,
"data": {
"id": "rfd_9xh0XQ5wu2lxCSUGajfv",
"transaction": "trx_CSUGajfv9xh0XQ5wu2lx",
"reference": "rma-881",
"amount": 2000,
"currency": "XAF",
"status": "succeeded",
"reason": "product_not_received"
}
}

Deduplicate 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 paymentRefund outcome
000000Succeeds
000009Payment succeeds, refund always fails. The one to test against
000001, 000002, 000003, 000004Nothing to refund: the payment itself never succeeds, so the refund call answers 422
Anything elseSucceeds

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_reasonWhat it means
insufficient_fundsThe provider's float could not cover the refund
refund_rejected_by_issuerA card issuer refused it
transaction_already_reversedThe payment was already reversed or refunded at the provider
invalid_numberThe payer's number or account cannot receive it, for instance closed or not a subscriber. Live only
limit_exceededA limit on the receiving account or at the provider was reached. Live only
network_errorThe provider could not be reached in time
provider_errorThe provider was down, or refused the call itself
provider_declinedThe provider refused it without saying why
ops_failedWajub 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.

GEThttps://api.wajub.com/refunds
Query parameterAccepts
per_page1 to 100, defaults to 25
statuspending, processing, succeeded, failed, cancelled. Before API version 2026-10-12, a cancelled refund reads failed and failed lists it
searchFree text over the refund reference and id
date_from, date_toDates, date_to no earlier than date_from
cursorSwitches to cursor pagination, for exports

Retrieving a single refund takes its id.

GEThttps://api.wajub.com/refunds/{id}

What did you think of this content?