Skip to main content
When a withdrawal is successfully disbursed, it can still be reversed by the SPEI system. This guide explains when refunds occur, how Tonder handles them automatically, and how your integration should respond.
A withdrawal refund (refunded) is distinct from a payment refund. It means the disbursed funds were returned to your merchant sub-account — not to the end customer’s bank.

Prerequisites

Before reading this guide, ensure you are familiar with:

How Withdrawal Refunds Work

A refund occurs when the SPEI system reverses a previously completed disbursement. Tonder detects this automatically and transitions the withdrawal to refunded — no action is required from your integration to trigger it.
1

Withdrawal completes

The withdrawal reaches paid_full status, and a webhook notification is dispatched to your endpoint (after a brief delay — see PAID_FULL notification delay).
2

Provider signals reversal

The SPEI system notifies Tonder that the disbursement has been reversed. Tonder processes this automatically and transitions the withdrawal to refunded.
3

Status transitions to refunded

The withdrawal moves from paid_fullrefunded. This is a terminal state — no further transitions are possible.
4

Webhook notification delivered

A webhook is sent to your configured endpoint with status: "refunded". The refunded terminal notification also suppresses any pending paid_full webhook that has not yet been dispatched.
5

Funds returned to sub-account

The refunded amount is credited back to your merchant sub-account. You should notify the withdrawal recipient and initiate a new withdrawal request if appropriate.

Withdrawal Status Reference

Understanding the full status lifecycle helps you handle refunds correctly. The statuses relevant to the refund flow are:
StatusTypeDescription
pendingInitialWithdrawal request created
processingIntermediateApproved and being sent to provider
sent_to_providerIntermediateSubmitted to SPEI network
in_transitIntermediateFunds actively transferring
on_holdIntermediatePaused for manual review
paid_fullIntermediateDisbursement confirmed by provider
refundedTerminalFunds reversed and returned to sub-account
failedTerminalWithdrawal failed
cancelledTerminalWithdrawal cancelled
expiredTerminalWithdrawal timed out
Terminal statuses (refunded, failed, cancelled, expired) are final. Once a withdrawal reaches a terminal state, no further transitions can occur — including via the API.
The complete transition path to a refund is:
pending → processing → sent_to_provider → paid_full → refunded

Webhook Notifications for Refunds

Refund webhook payload

When a withdrawal is refunded, your webhook endpoint receives a notification with status: "refunded":
{
  "withdrawal_id": "wdr_xxxxxxxxxxxxxxxx",
  "status": "refunded",
  "amount": 1500.00,
  "currency": "MXN",
  "provider_reference": "12345678",
  "created_at": "2025-02-10T14:22:00Z",
  "updated_at": "2025-02-10T15:47:33Z"
}
withdrawal_id
string
Unique identifier for the withdrawal.
status
string
Will be refunded for a reversed disbursement.
provider_reference
string
The SPEI system transaction reference used to identify the inbound notification.
Tonder applies a short delay (default: 60 seconds) before sending the paid_full webhook. This is intentional: it allows time for the SPEI system to signal a reversal and suppress the paid_full notification if the disbursement is immediately reversed. This means your integration may observe only a refunded webhook — with no preceding paid_full webhook — for very fast reversals.
Design your integration to handle refunded arriving without a prior paid_full notification. Do not assume paid_full will always be received before refunded.

Notification ordering guarantees

ScenarioWebhooks you receive
Successful disbursement, no reversalpending → … → paid_full
Fast reversal (before paid_full webhook dispatches)pending → … → refunded
Reversal after paid_full webhook was already sentpending → … → paid_fullrefunded
Once a terminal webhook (refunded, failed, cancelled) has been sent, no further webhooks are dispatched for that withdrawal — including any delayed paid_full that may still be queued.

Handling Refunds in Your Integration

1

Acknowledge the webhook

Respond with HTTP 200 immediately upon receipt. Do not delay the response while processing business logic.
2

Check idempotency

Use the withdrawal_id to look up the withdrawal in your system. If you have already processed a refunded event for this withdrawal, skip further processing.
3

Credit funds to your customer

The refunded amount has been returned to your merchant sub-account. Credit the corresponding amount to the withdrawal recipient in your system.
4

Notify the recipient

Inform the withdrawal recipient that the transfer was reversed and that the funds have been returned to your sub-account.
5

Create a new withdrawal if appropriate

If the reversal was due to a correctable error (e.g., incorrect CLABE), allow the recipient to submit a new withdrawal request after verifying their account details.

Querying withdrawal status via API

You can confirm a withdrawal’s current status at any time using the Withdrawals API:
curl -X GET https://app.tonder.io/api/v1/withdrawals/{withdrawal_id}/ \
  -H "Authorization: Token YOUR_API_KEY" \
  -H "Content-Type: application/json"

Error Handling

Cause: Your integration attempted to perform an action (e.g., CANCEL) on a withdrawal that is already in a terminal state (refunded, failed, cancelled).Solution: Check the current withdrawal status before attempting any action. Once a withdrawal is refunded, no further actions are valid. Poll the status endpoint or rely on webhooks to stay up to date.
Cause: The SPEI provider reversed the disbursement before Tonder’s delayed paid_full notification was dispatched. This is expected behavior, not an error.Solution: Ensure your webhook handler can process refunded independently of paid_full. Do not rely on paid_full being received first. Use the withdrawal_id to correlate events regardless of order.
Cause: Webhook delivery uses at-least-once semantics. In rare cases, the same event may be delivered more than once.Solution: Implement idempotent webhook handling. Store processed withdrawal_id + status combinations and skip re-processing if the pair has already been handled.

Next Steps

Withdrawal Webhooks

Configure and verify webhook delivery for all withdrawal status changes

Create a Withdrawal

Learn how to initiate a new withdrawal after a refund

Withdrawal Status Reference

Full status definitions, transitions, and terminal state behavior

Dashboard — Transactions

Monitor and investigate refunded withdrawals in the Tonder Dashboard