> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tonder.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a Withdrawal

This guide explains how to send payouts, or withdrawals, to beneficiaries using the Tonder API. Withdrawals are processed through the same unified [Process Transaction](/reference/process-transaction) endpoint as payments, by setting the `operation_type` to `"withdrawal"`.

<Note>
  **Availability Notice**

  Withdrawals are currently only available for beneficiaries in Mexico. Support for additional countries will be added in future releases.
</Note>

<Info>
  **Available Transfer Methods**

  There are two transfer methods available:

  * SPEI: Bank transfers processed same business day during banking hours.
  * Debit Card: Instant deposits available within 5-15 minutes, 24/7.
</Info>

<Warning>
  **Critical: Geolocation Required for Mexico**

  The `metadata.latitude` and `metadata.longitude` fields are **mandatory** for processing withdrawals in Mexico. Omitting these fields or providing erroneous coordinates will result in a **failed transaction**. If the merchant cannot obtain the user's device geolocation, the merchant's own office location can be used instead, as long as it is within Mexican territory.
</Warning>

## Step 1: Choose Your Transfer Method

Select the transfer method that fits your payout needs. Here you can find a quick comparison between the two methods:

| Aspect          | SPEI                             | Debit Card                        |
| --------------- | -------------------------------- | --------------------------------- |
| Processing Time | Same business day                | 5-15 minutes                      |
| Availability    | Banking hours only               | 24/7                              |
| Account Type    | 18-digit CLABE                   | 16-digit card number              |
| Best For        | Regular payments, larger amounts | Urgent payments, instant cashouts |

### SPEI Bank Transfer

SPEI (Sistema de Pagos Electrónicos Interbancarios) is Mexico's standard electronic payment system for bank-to-bank transfers.

You should use SPEI when:

* Making regular business payments, such as commissions or vendor payments.
* Processing payroll and salary transfers.
* Sending larger amounts where cost-effectiveness is important.
* Same-day processing during business hours is acceptable.

<Info>
  **Key requirements**

  Pay attention to the following specifics for SPEI withdrawals:

  * The `transfer_method` value must be `"SPEI"`.
  * The `beneficiary.account` must be a valid 18-digit CLABE (Clave Bancaria Estandarizada).
  * Processing occurs during banking hours (typically 9 AM - 5 PM MXT, Monday-Friday).
</Info>

### Debit Card Instant Transfer

Debit card withdrawals provide near-instant fund delivery directly to the beneficiary's debit card.

You should use Debit Card withdrawals when:

* Making urgent or emergency payments that can't wait for business hours.
* Paying gig economy workers who need immediate access to funds.
* Providing instant cashouts or on-demand withdrawals.
* Processing customer refunds that require immediate availability.

<Info>
  **Key requirements**

  Pay attention to the following specifics for debit card withdrawals:

  * The `transfer_method` value must be `"DEBIT_CARD"`.
  * The `beneficiary.account` must be a valid 16-digit debit card number.
  * Processing occurs 24/7, including weekends and holidays.
  * Funds are typically available within 5-15 minutes.
</Info>

## Step 2: Make the Withdrawal Request

Create and send your withdrawal request using the [Process Transaction](/reference/process-transaction) endpoint with `operation_type` set to `"withdrawal"`.

The request must have the following fields:

| Field              | Description                                                               |
| ------------------ | ------------------------------------------------------------------------- |
| `operation_type`   | Always set to `"withdrawal"` for payout operations                        |
| `amount`           | The withdrawal amount in the specified currency                           |
| `currency`         | Currency code (currently only `"MXN"` is supported)                       |
| `client_reference` | Your internal reference identifier for this withdrawal                    |
| `transfer_method`  | Either `"SPEI"` or `"DEBIT_CARD"`                                         |
| `beneficiary`      | Complete beneficiary information object                                   |
| `metadata`         | Additional required information (must include `latitude` and `longitude`) |

The `beneficiary` object contains the recipient's information for the withdrawal. This object specifies who will receive the funds and how they should be delivered. You can see the properties of the `beneficiary` object in the table below:

| Field         | Description                                                                                                                            |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `account`     | The destination account number (18-digit CLABE for SPEI transfers, or 16-digit card number for debit card deposits)                    |
| `name`        | Full legal name of the beneficiary as it appears on their bank account or card                                                         |
| `institution` | Bank institution code (you can find codes in our [Mexican Banking Reference](/direct-integration/reference/mexican-banking-reference)) |
| `email`       | Beneficiary's email address for notifications and record-keeping                                                                       |
| `rfc`         | Mexican tax ID (RFC), used for compliance and verification                                                                             |
| `curp`        | Mexican CURP, 18-character alphanumeric personal identification number                                                                 |

<Note>
  **RFC and CURP requirements**

  Either `rfc` or `curp` must be provided for every withdrawal request. If neither is available, send `"ND"` (No Disponible) as the value for one of the two fields.
</Note>

Here is a confirmed working example for a SPEI transfer:

```json SPEI Transfer theme={null}
{
  "operation_type": "withdrawal",
  "amount": 6.00,
  "currency": "MXN",
  "client_reference": "card-payout-002",
  "transfer_method": "SPEI",
  "description": "Instant payout",
  "beneficiary": {
    "account": "846180000400000001",
    "name": "Ana María González",
    "rfc": "GOAN850315AB2",
    "institution": "40012",
    "email": "ana.gonzalez@email.com"
  },
  "metadata": {
    "latitude": "22.8870221",
    "longitude": "-109.911775",
    "orderId": "pruebaTonderPayout-00001"
  }
}
```

### Metadata Requirements

The `metadata` object is required for all withdrawal requests and must include geolocation information:

| Field       | Type   | Required | Description                                               |
| ----------- | ------ | -------- | --------------------------------------------------------- |
| `latitude`  | string | Yes      | Geographic latitude coordinate of the transaction origin  |
| `longitude` | string | Yes      | Geographic longitude coordinate of the transaction origin |

You can also include optional fields in the metadata object for tracking purposes: `operation_date`, `customer_email`, `business_user`, `customer_id`, or `order_id` (sent as `orderId` in the confirmed example above).

Make a POST request to the [Process Transaction](/reference/process-transaction) endpoint with your withdrawal data:

```bash theme={null}
curl -X POST https://stage.tonder.io/api/v1/process/ \
  -H "Authorization: Token YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "operation_type": "withdrawal",
    "amount": 6.00,
    "currency": "MXN",
    "client_reference": "card-payout-002",
    "transfer_method": "SPEI",
    "description": "Instant payout",
    "beneficiary": {
      "account": "846180000400000001",
      "name": "Ana María González",
      "rfc": "GOAN850315AB2",
      "institution": "40012",
      "email": "ana.gonzalez@email.com"
    },
    "metadata": {
      "latitude": "22.8870221",
      "longitude": "-109.911775",
      "orderId": "pruebaTonderPayout-00001"
    }
  }'
```

## Step 3: Handle the Response

Upon successful request, the API will return an immediate acknowledgment. Here you find a description of the fields in the response:

| Field                | Description                                        |
| -------------------- | -------------------------------------------------- |
| `id`                 | Unique transaction identifier                      |
| `operation_type`     | Confirms this is a withdrawal operation            |
| `status`             | Current transaction status (initially `PENDING`)   |
| `amount`             | Withdrawal amount as submitted                     |
| `currency`           | Currency code                                      |
| `merchant_reference` | Your internal reference identifier                 |
| `created_at`         | ISO 8601 timestamp when the withdrawal was created |
| `status_code`        | HTTP status code (201 for successful creation)     |

The response structure is as follows:

```json theme={null}
{
  "id": "550e8400-e29b-41d4-a716-446655440001",
  "operation_type": "withdrawal",
  "status": "PENDING",
  "amount": 6.00,
  "currency": "MXN",
  "merchant_reference": "card-payout-002",
  "created_at": "2024-07-26T10:30:00Z",
  "status_code": 201
}
```

After receiving the response, save the transaction ID from the `id` field to monitor progress.

## Step 4: Withdrawal Lifecycle and Statuses

Withdrawals are asynchronous operations. As your withdrawal progresses, it moves through the following statuses:

| Status             | Type         | Description                                                   |
| ------------------ | ------------ | ------------------------------------------------------------- |
| `PENDING`          | Initial      | The withdrawal request has been received and is queued        |
| `PROCESSING`       | Intermediate | The transfer is currently being approved and prepared         |
| `SENT_TO_PROVIDER` | Intermediate | The request has been sent to the bank or payment provider     |
| `PAID_FULL`        | Success      | The transfer was completed successfully                       |
| `REJECTED`         | Terminal     | The transfer was declined (for example, invalid account)      |
| `REFUNDED`         | Terminal     | Funds from a previously paid withdrawal were reversed by SPEI |

<Warning>
  `PAID_FULL` is not always final. A `PAID_FULL` (Success) withdrawal, and also a declined one, can still transition to `REFUNDED` if the SPEI system reverses the disbursement afterward. Do not treat `PAID_FULL` as the end of the lifecycle in your integration logic. See [Refunds](#refunds) below.
</Warning>

### Internal Status vs Webhook Status

Only two webhook notifications are sent per withdrawal: an initial Pending notification, and one terminal notification.

| Internal stage                                                    | Webhook status you receive         |
| ----------------------------------------------------------------- | ---------------------------------- |
| `PENDING`                                                         | Pending                            |
| `PROCESSING`, `SENT_TO_PROVIDER`                                  | No separate webhook, internal only |
| `PAID_FULL`                                                       | Success                            |
| `REJECTED` (rejected before reaching the provider)                | Declined                           |
| Failure after being sent to the provider                          | Failed                             |
| `REFUNDED` (can follow either a Success or a Declined withdrawal) | See [Refunds](#refunds) below      |

### Monitoring Options

You can monitor withdrawal status using one of these methods:

* Webhooks (Recommended): configure [webhooks](/direct-integration/webhooks/how-webhooks-works) to receive real-time status updates automatically.

* Status Polling: check transaction status periodically using the [Get Transaction Status](/reference/get-transaction-status) endpoint.

## Refunds

A withdrawal refund (`REFUNDED`) is distinct from a payment refund. It means the disbursed funds were returned to the merchant's sub account, not to the end customer's bank.

A refund occurs when the SPEI system reverses a previously completed or declined disbursement. Tonder detects this automatically and transitions the withdrawal to `REFUNDED` — no action is required from your integration to trigger it. This can happen after a `PAID_FULL` (Success) withdrawal, and also after a Declined one.

### How It Works

1. The withdrawal reaches `PAID_FULL` status. A webhook notification is dispatched to your endpoint after a brief delay.
2. If SPEI notifies Tonder that the disbursement has been reversed before that delay elapses, Tonder suppresses the pending `PAID_FULL` webhook and moves the withdrawal directly to `REFUNDED`.
3. If the reversal happens after the `PAID_FULL` webhook has already been sent, you will receive a separate `REFUNDED` webhook afterward.
4. `REFUNDED` is a terminal state — no further transitions occur.
5. The refunded amount is credited back to the merchant's sub account. Notify the withdrawal recipient and initiate a new withdrawal request if appropriate.

<Tip>
  Design your integration to handle a `REFUNDED` notification arriving without a prior `PAID_FULL` notification. Do not assume `PAID_FULL` will always be received first.
</Tip>

### Notification Ordering

| Scenario                                                    | Notifications you receive            |
| ----------------------------------------------------------- | ------------------------------------ |
| Successful disbursement, no reversal                        | `PENDING` → `PAID_FULL`              |
| Fast reversal, before the `PAID_FULL` webhook is dispatched | `PENDING` → `REFUNDED`               |
| Reversal after the `PAID_FULL` webhook was already sent     | `PENDING` → `PAID_FULL` → `REFUNDED` |

### Common Refund Reasons

| Reason                 | Description                                          |
| ---------------------- | ---------------------------------------------------- |
| Cuenta inexistente     | The destination account does not exist               |
| CLABE incorrecta       | The CLABE number is invalid or formatted incorrectly |
| Cuenta cancelada       | The destination account has been closed              |
| Fondos insuficientes   | Insufficient funds at the receiving institution      |
| Operación no permitida | The transfer type is not permitted for this account  |

This list is not exhaustive. Always surface the reason to your operations team for investigation.

### Handling a Refund Notification

1. Acknowledge the notification immediately (HTTP 200) — do not delay the response while processing business logic.
2. Use the withdrawal `id` to check for idempotency. If you already processed a `REFUNDED` event for this withdrawal, skip further processing.
3. Credit the refunded amount to the withdrawal recipient in your own system.
4. Notify the recipient that the transfer was reversed, including the reason if available.
5. If the reversal was due to a correctable error (for example, an incorrect CLABE), allow the recipient to submit a new withdrawal request after verifying their account details.

## Webhooks

Withdrawal webhooks for API Direct follow the same format as API Direct [payment webhooks](/direct-integration/webhooks/how-webhooks-works), with a few differences:

* `operation_type` is `"withdrawal"`
* `transfer_method_type` is used instead of `payment_method_type`
* `event_type` uses the `withdrawal_` prefix (for example, `withdrawal_Pending`, `withdrawal_Success`)
* `provider` is `"STP"`
* `metadata` contains the withdrawal fields defined by the merchant (`latitude`, `longitude`, `beneficiary_rfc`, and so on)

Two webhooks are always generated per withdrawal: first a Pending notification, then a terminal notification (Success, Declined, or Failed).

### Successful SPEI Withdrawal

<CodeGroup>
  ```json Pending theme={null}
  {
    "id": "f50e4a1f-7145-4737-836f-32250eee38c2",
    "operation_type": "withdrawal",
    "amount": "55",
    "currency": "MXN",
    "client_reference": "card-payout-002",
    "status": "Pending",
    "provider": "STP",
    "transfer_method_type": "SPEI",
    "created": "2026-02-27T06:41:50.338733Z",
    "metadata": {
      "latitude": "22.8870221",
      "longitude": "-109.911775",
      "beneficiary_rfc": "QUIG850315H42"
    },
    "event_type": "withdrawal_Pending",
    "action": "MODIFY"
  }
  ```

  ```json Success theme={null}
  {
    "id": "f50e4a1f-7145-4737-836f-32250eee38c2",
    "operation_type": "withdrawal",
    "amount": "55",
    "currency": "MXN",
    "client_reference": "card-payout-002",
    "status": "Success",
    "provider": "STP",
    "transfer_method_type": "SPEI",
    "created": "2026-02-27T06:41:50.338733Z",
    "metadata": {
      "latitude": "22.8870221",
      "longitude": "-109.911775",
      "beneficiary_rfc": "QUIG850315H42"
    },
    "event_type": "withdrawal_Success",
    "action": "MODIFY"
  }
  ```
</CodeGroup>

### Declined SPEI Withdrawal

A Declined withdrawal means the transfer was rejected by Tonder before being handed off to the provider.

<CodeGroup>
  ```json Pending theme={null}
  {
    "id": "c7b14546-84bd-4a81-89fb-3dc660f47011",
    "operation_type": "withdrawal",
    "amount": "60",
    "currency": "MXN",
    "client_reference": "card-payout-002",
    "status": "Pending",
    "provider": "STP",
    "transfer_method_type": "SPEI",
    "created": "2026-02-27T06:47:09.205388Z",
    "metadata": {
      "latitude": "22.8870221",
      "longitude": "-109.911775",
      "beneficiary_rfc": "QUIG850315H42"
    },
    "event_type": "withdrawal_Pending",
    "action": "MODIFY"
  }
  ```

  ```json Declined theme={null}
  {
    "id": "c7b14546-84bd-4a81-89fb-3dc660f47011",
    "operation_type": "withdrawal",
    "amount": "60",
    "currency": "MXN",
    "client_reference": "card-payout-002",
    "status": "Declined",
    "provider": "STP",
    "transfer_method_type": "SPEI",
    "created": "2026-02-27T06:47:09.205388Z",
    "metadata": {
      "latitude": "22.8870221",
      "longitude": "-109.911775",
      "beneficiary_rfc": "QUIG850315H42"
    },
    "event_type": "withdrawal_Declined",
    "action": "MODIFY"
  }
  ```
</CodeGroup>

### Failed SPEI Withdrawal

A Failed withdrawal is sent to the provider but STP is unable to process the transfer, which is different from Declined.

<Note>
  The examples below are illustrative and follow the standard API Direct webhook pattern.
</Note>

<CodeGroup>
  ```json Pending theme={null}
  {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "operation_type": "withdrawal",
    "amount": "75",
    "currency": "MXN",
    "client_reference": "card-payout-003",
    "status": "Pending",
    "provider": "STP",
    "transfer_method_type": "SPEI",
    "created": "2026-02-27T07:00:00.000000Z",
    "metadata": {
      "latitude": "22.8870221",
      "longitude": "-109.911775",
      "beneficiary_rfc": "QUIG850315H42"
    },
    "event_type": "withdrawal_Pending",
    "action": "MODIFY"
  }
  ```

  ```json Failed theme={null}
  {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "operation_type": "withdrawal",
    "amount": "75",
    "currency": "MXN",
    "client_reference": "card-payout-003",
    "status": "Failed",
    "provider": "STP",
    "transfer_method_type": "SPEI",
    "created": "2026-02-27T07:00:00.000000Z",
    "metadata": {
      "latitude": "22.8870221",
      "longitude": "-109.911775",
      "beneficiary_rfc": "QUIG850315H42"
    },
    "event_type": "withdrawal_Failed",
    "action": "MODIFY"
  }
  ```
</CodeGroup>

### Best Practices

1. Idempotency: handle duplicate notifications gracefully using the withdrawal `id`.
2. Acknowledgment: return HTTP 200 quickly, then process asynchronously.
3. Two notifications per withdrawal: always expect a Pending notification followed by exactly one terminal notification (Success, Declined, or Failed).
4. Logging: log all notification receipts for debugging and audit purposes.

## Testing

Use the following institution codes in the Stage environment:

| Method      | Institution code |
| ----------- | ---------------- |
| SPEI        | `97846`          |
| DEBIT\_CARD | `97846`          |

For production, use real institution codes from the [Mexican Banking Reference](/direct-integration/reference/mexican-banking-reference).

## Next Steps

* Set up [webhooks](/direct-integration/webhooks/how-webhooks-works) to receive real-time notifications when withdrawal statuses change.
* Check out [HTTP response codes](/direct-integration/http-response-codes) to understand how to handle different API responses and error scenarios.
* Explore [rate limits](/direct-integration/rate-limits) to ensure your withdrawal processing doesn't exceed API usage limits.
