This guide explains the structure of webhook notifications sent by the Tonder Withdrawals API when withdrawal statuses change.
Webhook Overview
Webhooks are HTTP POST requests sent to your configured endpoint whenever a withdrawal status changes. They provide real-time notifications so you don’t need to poll the API for status updates.
Notification Structure
The webhook notification is an object containing the full withdrawal details, with an audit trail in the status_changes array.
Payload Structure
The webhook payload contains the complete withdrawal object with all details:
Withdrawal Object
| Field | Type | Description |
|---|
id | string | Unique withdrawal identifier |
user_id | string | User ID in the Tonder system |
business_id | string | Business/merchant identifier |
currency | string | Currency code (e.g., “MXN”) |
amount | decimal | Withdrawal amount |
status | string | Current withdrawal status |
transfer_method | string | Transfer method (“SPEI” or “DEBIT_CARD”) |
created_at | string | ISO 8601 timestamp of creation |
modified_at | string | ISO 8601 timestamp of last modification |
provider_reference | string | Reference from the payment provider |
fixed_fee | string | Fixed fee amount |
tax | string | Tax percentage |
fee_amount | string/null | Calculated fee amount |
Account Data Object
The account_data object includes complete bank and account details:
| Field | Type | Description |
|---|
cuenta_ordenante | string | Originator account number (CLABE) |
nombre_ordenante | string | Originator name |
rfc_curp_ordenante | string | Originator RFC or CURP |
cuenta_beneficiario | string | Beneficiary account number |
nombre_beneficiario | string | Beneficiary name |
rfc_curp_beneficiario | string | Beneficiary RFC or CURP |
institucion_contraparte | string | Counterparty institution code |
tipo_cuenta_beneficiario | integer | Beneficiary account type (40 for SPEI, 3 for DEBIT_CARD) |
email | string | Contact email |
concepto_pago | string | Payment concept/description |
Status Changes Array
The status_changes array provides a complete audit trail of status transitions:
| Field | Type | Description |
|---|
from_status | string | Previous status |
to_status | string | New status |
timestamp | string | ISO 8601 timestamp of the change |
reason | string (optional) | Reason for the status change |
The metadata object contains additional information:
| Field | Type | Description |
|---|
latitude | string | Geographic latitude |
longitude | string | Geographic longitude |
operation_date | string | Operation date (optional) |
customer_email | string | Customer email (optional) |
business_user | string | Business user identifier (optional) |
customer_id | string | Customer ID (optional) |
order_id | string | Order ID (optional) |
Example Webhook Payload
The webhook payload is the complete withdrawal object. Here’s an example:
{
"id": "40f19a6b-4ce4-424e-92fe-1b564c07dbd7",
"user_id": "1",
"business_id": "21",
"currency": "MXN",
"amount": 100.0,
"status": "PROCESSING",
"account_data": {
"cuenta_ordenante": "646180567300000006",
"nombre_ordenante": "TRES_COMAS",
"rfc_curp_ordenante": "ND",
"cuenta_beneficiario": "846180000400000001",
"nombre_beneficiario": "Fabio",
"rfc_curp_beneficiario": "ND",
"institucion_contraparte": "97846",
"tipo_cuenta_beneficiario": 40,
"email": "fabio@tonder.io",
"concepto_pago": "test"
},
"status_changes": [
{
"from_status": null,
"to_status": "PENDING",
"timestamp": "2025-10-02T01:09:37.371365Z",
"reason": "Withdrawal request created"
},
{
"from_status": "PENDING",
"to_status": "PROCESSING",
"timestamp": "2025-10-02T01:10:00.000000Z",
"reason": "Withdrawal approved and processing started"
}
],
"action": null,
"metadata": {
"latitude": "22.8870221",
"longitude": "-109.911775",
"operation_date": "2024-01-15",
"customer_email": "fabio@tonder.io",
"business_user": "admin_user_001",
"customer_id": "CUST_12345",
"order_id": "ORDER_ABC_789"
},
"created_at": "2025-10-02T01:09:37.371365Z",
"modified_at": "2025-10-02T01:10:00.000000Z",
"provider_reference": "PENDING",
"transfer_method": "SPEI",
"fixed_fee": "3.5",
"tax": "16.0",
"fee_amount": null
}
Status Change Examples
The webhook payload includes the complete withdrawal object with updated status. The status_changes array shows the transition history.
PENDING → PROCESSING
{
"id": "40f19a6b-4ce4-424e-92fe-1b564c07dbd7",
"status": "PROCESSING",
"status_changes": [
{
"from_status": "PENDING",
"to_status": "PROCESSING",
"timestamp": "2025-10-02T01:10:00.000000Z",
"reason": "Withdrawal approved and processing started"
}
],
"modified_at": "2025-10-02T01:10:00.000000Z",
...
}
PROCESSING → SENT_TO_PROVIDER
{
"id": "40f19a6b-4ce4-424e-92fe-1b564c07dbd7",
"status": "SENT_TO_PROVIDER",
"status_changes": [
{
"from_status": "PROCESSING",
"to_status": "SENT_TO_PROVIDER",
"timestamp": "2025-10-02T01:15:00.000000Z"
}
],
"modified_at": "2025-10-02T01:15:00.000000Z",
...
}
SENT_TO_PROVIDER → PAID_FULL
{
"id": "40f19a6b-4ce4-424e-92fe-1b564c07dbd7",
"status": "PAID_FULL",
"status_changes": [
{
"from_status": "SENT_TO_PROVIDER",
"to_status": "PAID_FULL",
"timestamp": "2025-10-02T02:00:00.000000Z",
"reason": "Transfer completed successfully"
}
],
"modified_at": "2025-10-02T02:00:00.000000Z",
"provider_reference": "SPEI-20251002-001234",
...
}
PENDING → REJECTED
{
"id": "40f19a6b-4ce4-424e-92fe-1b564c07dbd7",
"status": "REJECTED",
"status_changes": [
{
"from_status": "PENDING",
"to_status": "REJECTED",
"timestamp": "2025-10-02T01:09:45.000000Z",
"reason": "Invalid beneficiary account"
}
],
"modified_at": "2025-10-02T01:09:45.000000Z",
...
}
Webhook requests include standard HTTP headers. You may also receive custom headers for verification:
POST /your-webhook-endpoint HTTP/1.1
Host: your-server.com
Content-Type: application/json
X-Tonder-Event: withdrawal.status_changed
X-Tonder-Signature: <signature-for-verification>
Webhook Security
Verify Webhook AuthenticityAlways verify that webhooks are coming from Tonder. Check the signature header if provided, and validate the request source IP addresses.
Handling Webhooks
Best Practices
- Idempotency: Handle duplicate webhooks gracefully using the withdrawal ID
- Acknowledgment: Return HTTP 200 status quickly, then process asynchronously
- Retry Logic: Implement retry logic for failed webhook processing
- Logging: Log all webhook receipts for debugging and audit purposes
- Status Updates: Update your local records based on webhook status changes
Error Handling
If your webhook endpoint returns an error (4xx or 5xx), Tonder will retry the webhook delivery according to the retry policy. Ensure your endpoint can handle:
- Network timeouts
- Temporary server errors
- Duplicate deliveries
- Out-of-order deliveries
Testing Webhooks
When testing in the Stage environment:
- Use the test institution code (97846)
- Verify webhook payload structure
- Test all status transitions
- Validate error scenarios
See Testing Requirements for more details.
Next Steps