> ## 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.

# Webhook Payloads

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     |

### Metadata Object

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:

```json theme={null}
{
  "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

```json theme={null}
{
  "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

```json theme={null}
{
  "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

```json theme={null}
{
  "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

```json theme={null}
{
  "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 Headers

Webhook requests include standard HTTP headers. You may also receive custom headers for verification:

```http theme={null}
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

<Warning>
  **Verify Webhook Authenticity**

  Always verify that webhooks are coming from Tonder. Check the signature header if provided, and validate the request source IP addresses.
</Warning>

## Handling Webhooks

### Best Practices

1. **Idempotency**: Handle duplicate webhooks gracefully using the withdrawal ID
2. **Acknowledgment**: Return HTTP 200 status quickly, then process asynchronously
3. **Retry Logic**: Implement retry logic for failed webhook processing
4. **Logging**: Log all webhook receipts for debugging and audit purposes
5. **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](/withdrawals-api/reference/testing) for more details.

## Next Steps

* Learn about [system architecture](/withdrawals-api/webhooks/architecture) to understand how webhooks are generated
* Review [withdrawal workflows](/withdrawals-api/guides/workflow-statuses) to understand status transitions
* Check [testing requirements](/withdrawals-api/reference/testing) to validate webhook handling
