Withdrawals

Send money to a beneficiary via SPEI or debit card, and track the status through to settlement.

Withdrawals (payouts) to beneficiaries are processed through the same unified /process/ endpoint as payments, using operation_type: "withdrawal".

Availability. Withdrawals via API Direct are currently only available for beneficiaries in Mexico.

Available transfer methods

AspectSPEIDebit Card
Processing timeInstant (seconds)Instant (seconds)
Availability24/724/7
Account type18-digit CLABE16-digit card number
Best forBeneficiaries with a CLABE, larger amountsBeneficiaries who only have a card

The payout rail is set on your account. Tonder provisions your account on a payout rail — STP or Fintoc — for each transfer method. Both rails support SPEI and debit card. The request body is identical on both: there is no provider or rail field to send. The rail only shows up in the provider value of the webhooks.

Geolocation required for Mexico. The metadata.latitude and metadata.longitude fields are mandatory for processing withdrawals in Mexico. Omitting them or sending erroneous coordinates results in a failed transaction.

Step 1: make the withdrawal request

Send your request to the /process/ endpoint with operation_type set to "withdrawal":

FieldDescription
operation_typeAlways "withdrawal" for payout operations.
amountThe withdrawal amount in the specified currency.
currencyCurrency code (currently only "MXN" is supported).
client_referenceYour internal reference identifier for this withdrawal.
transfer_methodEither "SPEI" or "DEBIT_CARD".
beneficiaryComplete beneficiary information object.
metadataAdditional required information (must include latitude and longitude).

The beneficiary object specifies who receives the funds and how:

FieldDescription
accountDestination account (18-digit CLABE for SPEI, or 16-digit card number for debit card).
nameBeneficiary's full legal name, as it appears on their account or card.
rfcBeneficiary's RFC.
curpBeneficiary's CURP (18-character alphanumeric code).
institutionBank institution code. See Banking reference.
emailBeneficiary's email for notifications and record-keeping.

RFC/CURP requirement. You must provide either rfc or curp on every withdrawal request. If you don't have one of the two available, send "ND" (Not Available) as the value for that field.

Institution codes for Stage testing depend on your payout rail. On the STP rail, use 97846 as the institution to simulate both SPEI and debit card withdrawals. On the Fintoc rail, 97846 is rejected with invalid_institution_id: use a real 5-digit Banxico code instead, such as 40012. The test accounts for each rail are in Test cases — Withdrawals. In production, use the real codes from the Banking reference.

Confirmed, working example — SPEI transfer:

{
  "operation_type": "withdrawal",
  "amount": 20.00,
  "currency": "MXN",
  "client_reference": "payout-001",
  "transfer_method": "SPEI",
  "description": "Commission payment",
  "beneficiary": {
    "account": "846180000400000001",
    "name": "Ana María González",
    "rfc": "GOAN850315AB2",
    "institution": "97846",
    "email": "ana.gonzalez@email.com"
  },
  "metadata": {
    "latitude": "22.8870221",
    "longitude": "-109.911775"
  }
}
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": 20.00,
    "currency": "MXN",
    "client_reference": "payout-001",
    "transfer_method": "SPEI",
    "description": "Commission payment",
    "beneficiary": {
      "account": "846180000400000001",
      "name": "Ana María González",
      "rfc": "GOAN850315AB2",
      "institution": "97846",
      "email": "ana.gonzalez@email.com"
    },
    "metadata": {
      "latitude": "22.8870221",
      "longitude": "-109.911775"
    }
  }'

Step 2: handle the response

On a successful request, the API returns an immediate acknowledgment:

{
  "id": "550e8400-e29b-41d4-a716-446655440001",
  "withdrawal_id": "dc5f481c-6219-438b-9612-7ca1bba6864e",
  "operation_type": "withdrawal",
  "status": "Pending",
  "amount": 20.00,
  "currency": "MXN",
  "client_reference": "payout-001",
  "created_at": "2026-10-01T17:33:41.364685Z",
  "status_code": 201
}
FieldDescription
idUnique transaction identifier — store it to monitor progress.
withdrawal_idIdentifier used to approve, reject and refund the withdrawal. Different from id.
operation_typeConfirms this is a withdrawal operation.
statusCurrent transaction status (initially Pending).
amountWithdrawal amount as submitted.
currencyCurrency code.
client_referenceYour internal reference identifier.
created_atISO 8601 timestamp when the withdrawal was created.
status_codeHTTP status code (201 for successful creation).

The response carries two different identifiers. id is the transaction: use it to check the status. withdrawal_id is the withdrawal itself: use it to approve, reject or refund it. Store both.

Step 3: approve the withdrawal

Unless automatic approval is enabled for your account, a withdrawal does not advance on its own: it stays in Pending until it is approved. If your withdrawals stay in Pending, this is the step that is missing.

From the Dashboard. Open the withdrawal detail in Withdrawals and press Approve.

By API:

curl -X POST 'https://stage.tonder.io/api/v1/withdrawals/perform_action/' \
  -H 'Authorization: Token YOUR_TEST_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{ "id": "dc5f481c-6219-438b-9612-7ca1bba6864e", "action": "approve" }'
curl -X POST 'https://app.tonder.io/api/v1/withdrawals/perform_action/' \
  -H 'Authorization: Token YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{ "id": "dc5f481c-6219-438b-9612-7ca1bba6864e", "action": "approve" }'

Available actions: approve, reject, cancel, fail, refund.

Once approved, the withdrawal advances on its own: Pending → Processing → Success.

The id in this body is the withdrawal_id, not the transaction id, even though the field is named id. Sending the wrong one returns 404 Withdrawal not found.

Step 4: check the transaction status

Withdrawals are asynchronous operations. Use the id from the response to check the status at the GET /api/v1/transactions/{id}/ endpoint, or monitor via webhooks.

As your withdrawal progresses, it moves through different statuses:

StatusTypeDescriptionPreviously
PendingInitialThe request was received and is queued or on hold.PENDING, ON_HOLD
ProcessingIntermediateThe request was sent to the bank or payment provider.SENT_TO_PROVIDER
SuccessSuccessThe transfer completed successfully.PAID_FULL
DeclinedTerminalThe withdrawal was rejected (e.g., invalid account).REJECTED
CancelledTerminalThe withdrawal was cancelled.CANCELED
FailedTerminalThe withdrawal failed.FAILED
ReturnedTerminalThe transfer was sent and processed, but the funds were subsequently returned by the receiving bank.REFUNDED

Renamed statuses. Withdrawals now use the same status vocabulary as payments. If your integration expected the previous names (PENDING, SENT_TO_PROVIDER, PAID_FULL, REJECTED, CANCELED, FAILED, REFUNDED), update it using the "Previously" column in the table.

Treat Returned as final. A Returned withdrawal already reached Success before the funds came back, so a handler that only credits on Success will never see the reversal. Reconcile on Success, Failed, Declined, Cancelled and Returned — all five are terminal.

Webhooks

API Direct withdrawal webhooks follow the same flat payload as payment webhooks, with these differences:

FieldDifference
operation_type"withdrawal".
transfer_method_typeUsed instead of payment_method_type (SPEI or DEBIT_CARD).
event_typeUses the withdrawal_ prefix (withdrawal_Pending, withdrawal_Success, withdrawal_Declined, withdrawal_Failed, withdrawal_Returned).
providerThe payout provider assigned to your account — currently STP or FINTOC. It depends on your account's rail; do not hardcode it and treat the list as open-ended.

Two webhooks are generated per withdrawal: first a Pending notification, then one terminal notification (Success, Declined, or Failed). If the receiving bank returns the funds after Success, a third one follows: withdrawal_Returned.

The provider value is STP or FINTOC depending on your account's rail. Both carry identical event semantics — match on event_type and status, not on provider.

Pending

{
  "id": "550e8400-e29b-41d4-a716-446655440001",
  "operation_type": "withdrawal",
  "amount": "20",
  "currency": "MXN",
  "client_reference": "payout-001",
  "status": "Pending",
  "provider": "STP",
  "transfer_method_type": "SPEI",
  "created": "2026-05-21T19:15:32.029134Z",
  "metadata": {
    "latitude": "22.8870221",
    "longitude": "-109.911775",
    "external_id": "payout-001"
  },
  "event_type": "withdrawal_Pending",
  "action": "MODIFY"
}

Success

{
  "id": "550e8400-e29b-41d4-a716-446655440001",
  "operation_type": "withdrawal",
  "amount": "20",
  "currency": "MXN",
  "client_reference": "payout-001",
  "status": "Success",
  "provider": "STP",
  "transfer_method_type": "SPEI",
  "created": "2026-05-21T19:20:47.029134Z",
  "metadata": {
    "latitude": "22.8870221",
    "longitude": "-109.911775",
    "external_id": "payout-001"
  },
  "event_type": "withdrawal_Success",
  "action": "MODIFY"
}

Declined

{
  "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": "DEBIT_CARD",
  "created": "2026-05-21T19:20:47.029134Z",
  "metadata": {
    "latitude": "22.8870221",
    "longitude": "-109.911775",
    "external_id": "card-payout-002"
  },
  "event_type": "withdrawal_Declined",
  "action": "MODIFY"
}

Failed

{
  "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": "DEBIT_CARD",
  "created": "2026-05-21T19:20:47.029134Z",
  "metadata": {
    "latitude": "22.8870221",
    "longitude": "-109.911775",
    "external_id": "card-payout-003"
  },
  "event_type": "withdrawal_Failed",
  "action": "MODIFY"
}

Returned

Sent only when the funds come back after Success — the third webhook on that withdrawal.

{
  "id": "550e8400-e29b-41d4-a716-446655440001",
  "operation_type": "withdrawal",
  "amount": "20",
  "currency": "MXN",
  "client_reference": "payout-001",
  "status": "Returned",
  "provider": "STP",
  "transfer_method_type": "SPEI",
  "created": "2026-05-22T04:26:32.523705Z",
  "metadata": {
    "latitude": "22.8870221",
    "longitude": "-109.911775",
    "external_id": "payout-001"
  },
  "event_type": "withdrawal_Returned",
  "action": "MODIFY"
}

Fintoc-rail accounts receive the same payloads with "provider": "FINTOC".

Recommended monitoring: use webhooks to receive real-time status updates. Alternatively, you can poll the transaction status endpoint.

Check your balance

Before dispersing, check the available balance:

curl https://stage.tonder.io/api/v1/withdrawals/balance \
  -H "Authorization: Token YOUR_API_KEY"
curl https://app.tonder.io/api/v1/withdrawals/balance \
  -H "Authorization: Token YOUR_API_KEY"
{
  "message": "Data retrieved successfully",
  "current_balance": "32894.55"
}

A withdrawal for more than the available balance is rejected. The endpoint's schema is in the API Reference.

Next steps

Was this page helpful?

On this page