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",
  "reason": "Cuenta inexistente",
  "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.
reason
string
The reversal reason provided by the SPEI system (e.g., "Cuenta inexistente", "CLABE incorrecta"). May be "Unknown error" if no reason was provided.
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 provide the reason if available (from the reason field).
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.
Cause: The SPEI system returned a reversal notification without a reason value.Solution: Treat this as an unspecified reversal. Present a generic message to the recipient (e.g., “The transfer was returned by the recipient’s bank. Please verify the account details and try again.”). Contact soporte@tonder.io if you need additional details for investigation.

Common Refund Reasons

The reason field in the webhook payload reflects the reversal reason returned by the SPEI system. Common values include:
ReasonDescription
Cuenta inexistenteThe destination account does not exist
CLABE incorrectaThe CLABE number is invalid or formatted incorrectly
Cuenta canceladaThe destination account has been closed
Fondos insuficientesInsufficient funds at the receiving institution
Operación no permitidaThe transfer type is not permitted for this account
This list is not exhaustive. The SPEI system may return additional reason values. Always surface the reason field to your operations team for investigation.

Next Steps