Disputes
When a payer contests a payment, how you respond, and what a loss costs you.
A dispute is a payer, or their card network, telling Wajub that a payment was wrong. It is opened against a single payment and ends one of two ways: you keep the money, or it goes back and your balance is debited.
Unlike a refund, you do not start a dispute and you cannot create one through the API. Your side of it is answering.
Dispute endpoints need the secret key
Every call on this page requires a secret key and answers 406 Not Acceptable with
Private Key Required to a public key. They belong on your server.
Where a dispute comes from
A dispute reaches you from one of two places.
The payer opens it from Pocket, the Wajub wallet where they can see the payments they have made. They pick a reason, describe the problem, and that description becomes the first message on the dispute thread.
| Reason the payer picks | What they are claiming |
|---|---|
product_not_received | They paid and nothing arrived |
product_not_as_described | What arrived was not what was sold |
duplicate_charge | They were charged twice for one thing |
fraudulent | They did not make this payment |
other | Anything else, explained in the description |
A dispute from Pocket carries the full amount of the payment, and a due_date to answer by,
seven days after it opened.
The provider reports a chargeback. When a card network or a provider notifies a chargeback on
one of your payments, Wajub opens a dispute for it. It carries the provider's own
provider_reference, its reason code in reason, and the amount the provider disputes, which can
be less than the payment. It also carries a due_date: the provider's deadline when it sends one,
seven days after opening otherwise.
Either way, a payment has one dispute in flight at a time. A second notice for the same case does not open a second dispute.
Disputes do not exist in sandbox
Disputes come from real payers and real providers, so a test payment never produces one. This flow cannot be rehearsed end to end before you go live. Build your handler against the payloads on this page, and check it against the first real dispute you receive.
The amount is held while the dispute is open
A dispute opened from Pocket, on a payment collected through Wajub's own provider, moves the disputed amount from your available balance to your disputed balance, as far as your available balance allows. Winning moves it back. Losing debits it, as described below.
The states a dispute moves through
A dispute lands open. Submitting evidence moves it to under_review on its own, you do not set
that yourself. From either of those two states it can end as won or lost, and both are final.
Wajub adjudicates. There is no endpoint that lets you declare yourself the winner; what you can do is put the evidence in front of the people who decide, or concede.
A resolved status you will not see
The list endpoint accepts ?status=resolved, but that status is never returned. Filter on won
and lost for finished disputes.
Respond with evidence
This is the part that decides the outcome, and it is worth being thorough. Each piece of evidence is filed under a type, and you can post several calls to build up a case.
https://api.wajub.com/disputes/{id}/submit-evidenceevidence_typeenumrequiredcustomer_communication, proof_of_delivery, service_documentation, receipt, product_description, cancellation_policy, refund_policy, duplicate_charge_documentation, other.evidence_textstringoptionalevidence_filefileoptionalmultipart/form-data.The type is a slot, not a label. Post twice with proof_of_delivery and the second call replaces
the first, it does not add to it. Use a different type for each distinct piece, and put everything
you want to say about one piece into that one call.
Send a text, a file, or both: a call with neither answers 422, and the dispute stays where it
was. Without a file, the piece is stored as your text alone: evidence.proof_of_delivery is then a
string rather than an object. A file is stored under a name of its own, with the extension of its
content; the name you gave it comes back as file_name.
Files are private: no address opens one on its own. Every answer of the API that returns the
dispute gives each file, evidence and message attachments alike, a url, a signed link valid 30
minutes. Past that, read the dispute again for a fresh one. A webhook carries file_path and no
link, since it is kept and resent long after a link would have expired.
Because a file is involved, this is a form upload rather than a JSON body.
curl https://api.wajub.com/disputes/dsp_hrgUOE225KMKULRQqYvlh/submit-evidence \
-H "Authorization: sk.xxxxxxxxxxxxxxxx…" \
-F "evidence_type=proof_of_delivery" \
-F "evidence_text=Delivered 12 Sept, signed for by the customer." \
-F "evidence_file=@/path/to/delivery-note.pdf"The call answers 200 OK with the dispute as it now stands, already moved to under_review, and
your evidence collected under evidence.
Evidence can only be filed while the dispute is open or under_review. Once it is won or
lost, the call answers 422 and the case is shut.
Evidence stays with Wajub
Every dispute carries evidence_reaches_provider, and today it is false on all of them. What you
file is recorded at Wajub and read by the people who rule on a Pocket dispute. It is not forwarded
to the provider or the card network, so on a chargeback your defence has to go to the provider
through your own account with them.
Conceding, and a call that does not do what its name suggests
If the payer is right, concede early rather than letting it run.
https://api.wajub.com/disputes/{id}/acceptAccepting takes an optional merchant_response, up to 2 000 characters, which is recorded against
the dispute. It sets the dispute to lost immediately and there is no way back, and it sends the
money back to the payer: Wajub creates a refund of the disputed amount, capped to what is still
refundable on the payment.
That refund carries the reason customer_complaint and the dispute's own reason in
metadata.dispute_reason, and it gives the payer back everything they paid for that amount, a fee
they paid on top included. If the refund cannot be created, the call fails and the dispute stays as
it was.
Three cases create no refund, and the dispute still ends lost:
- A chargeback. The payer's bank has already returned the money and the provider takes it back from Wajub, so a refund would pay the payer twice.
- Nothing left to refund. Earlier refunds already returned the whole payment.
- A dispute in another currency than its payment. There is no amount to refund in the payment's own currency.
close is accept under another name
POST /disputes/{id}/close is not a neutral way to tidy a dispute away. It has exactly the same
effect as accept: the dispute becomes lost, the payer is refunded unless it is a chargeback,
and the outcome is final. If you were looking for a call that dismisses a dispute in your favour,
there isn't one. Submit evidence instead.
Talk to the payer
A dispute carries a message thread, opened by the payer's own description. You can reply on it.
https://api.wajub.com/disputes/{id}/messagesMessages take content, required and up to 5 000 characters, and an optional attachments array
of files under the same limits as evidence. Each attachment comes back with file_name,
file_path and the same 30-minute url. POST /disputes/{id}/send-message is an alias for the
same thing. Your messages are recorded with a sender_type of merchant, the payer's with
customer, and the whole thread comes back on a retrieve. The thread stays open after the outcome:
a message on a dispute already won or lost is accepted and recorded, but changes nothing about
the result.
Reading disputes
Retrieving one returns it with its transaction, its customer and its full message thread.
https://api.wajub.com/disputes/{id}https://api.wajub.com/disputes| Query parameter | Accepts |
|---|---|
per_page | 1 to 100, defaults to 25 |
status | open, under_review, won, lost |
search | Free text over the dispute id, reason and description |
date_from, date_to | Dates, date_to no earlier than date_from |
cursor | Switches to cursor pagination |
Beyond the obvious fields, a dispute carries description from the payer, your
merchant_response, the evidence you filed, resolved_at once it is finished, and metadata.
How due_date is set
Every dispute carries due_date, the deadline to answer by. On a dispute opened from Pocket, it
is seven days after the opening. On a provider chargeback, it is the provider's deadline when it
sends one, seven days after the opening otherwise. Past its deadline, a Pocket dispute stays open
until Wajub decides it.
What a loss costs
On a payment collected through Wajub's own provider, a lost dispute takes the disputed amount off your balance, capped at whatever has not already left through a refund on the same payment, so the money cannot be taken twice. On a payment collected through your own provider account, Wajub holds none of the money, and the provider settles the chargeback with you directly.
A flat dispute fee is charged on top, 15 000 XAF by default in the dispute's currency, when the
loss comes from the API, with accept or close, or from a provider chargeback. Accepting from
the Dashboard does not charge it. So losing a 25 000 XAF dispute through the API costs 40 000 XAF,
not 25 000. The debits happen on the status change itself, not on anything you call afterwards.
A dispute does not change the original payment's status. The refund created when you accept shows
in it like any other refund, from API version 2026-10-11. When you accept, the
refund Wajub creates for the payer is an ordinary refund, with its own refund.* events, except
that it does not take the disputed amount a second time: the loss took it once, whichever is
recorded first, and the refund sends it to the payer. If the payer paid Wajub's fee on top of the
price (bearer: customer), that fee is debited from you as well, since the payer gets back
everything they paid. The money movement shows on your balance, which is where you reconcile
it.
Losing also feeds back into risk scoring: a lost dispute raises the assessed risk of the payer behind it, so Shield weighs future payments from them more heavily.
Webhooks
Seven events, and the ones worth acting on are the outcomes, plus the reminder.
| Event | Fires when |
|---|---|
dispute.created | A dispute was opened, by a payer or from a chargeback. Read it and decide how to answer |
dispute.updated | Any field changed, including every status change |
dispute.won | Resolved in your favour, nothing leaves your balance |
dispute.lost | Resolved against you, the amount is debited |
dispute.closed | Sent together with dispute.lost. A won dispute does not send it |
dispute.deadline_approaching | The deadline is two days away and the dispute is still open or under_review. Sent once, with metadata.reminded_at set; answer it now |
dispute.deleted | The dispute record was removed |
dispute.deadline_approaching is sent in live and in sandbox, for every dispute with a
due_date: Pocket disputes and chargebacks alike. Writing metadata.reminded_at on the
dispute also sends a dispute.updated beside it.
A loss sends you three webhooks
Going to lost emits dispute.updated, dispute.lost and dispute.closed, in that order, for
the same event. Pick one to act on, dispute.lost, and let the other two pass. Acting on
dispute.closed as well will debit your own books twice.
dispute.createdeventDeduplicate on X-Wajub-Delivery-Id, as everywhere else. Registration and signature verification
are in Webhooks.
In the Dashboard
Disputes lists them with their status and the payment behind each one. Opening a dispute gives you the payer's description, the message thread, and the same three actions the API exposes plus one it does not: you can mark a dispute as won from here when Wajub has adjudicated in your favour.
The section needs the manage_disputes permission, which is separate from manage_payments, so a
teammate can run orders without being able to concede a dispute on your behalf.
Fewer disputes is cheaper than winning them
A dispute costs you time whatever the outcome, so the work that pays best happens before one exists.
Refund willingly when a customer complains and you have no case: a refund you choose costs you the amount, a dispute you lose costs the amount plus the flat dispute fee. Keep proof of delivery where you can attach it in one click. And let Shield reject the payments most likely to come back at you, since a payment refused up front cannot be disputed at all.
Related pages
- Shield, fraud detectionReject risky payments before they can be disputed.
- RefundsGive the money back on your terms instead of theirs.
- Balance and settlementsWhere the debit from a lost dispute lands.
- WebhooksRegister an endpoint and verify the signature on dispute events.
- Security best practicesLimit fraud on the integration side.
- Disputes API referenceEvery field on the resource, and the six endpoints.