Webhooks provide real-time notifications about transaction status changes and allow you to configure endpoints for receiving these notifications. Tonder’s webhook system ensures reliable delivery of transaction updates through comprehensive retry mechanisms and authentication options.

Webhook Management

Manage webhook endpoints through the API to receive real-time transaction notifications.

Create Webhook

POST /webhooks/
Creates a new webhook endpoint for your business.
{
  "url": "https://your-site.com/webhook",
  "auth_method": "BEARER",
  "credentials": {
    "token": "your_bearer_token"
  }
}

List Webhooks

GET /webhooks/
Retrieves all webhook endpoints configured for your business.
[
  {
    "id": 123,
    "url": "https://your-site.com/webhook",
    "status": "active",
    "auth_method": "BEARER",
    "credentials": {
      "token": "your_bearer_token"
    }
  },
  {
    "id": 124,
    "url": "https://backup.your-site.com/webhook",
    "status": "inactive",
    "auth_method": "API_TOKEN",
    "credentials": {
      "header": "X-API-Key",
      "token": "your_api_key"
    }
  }
]

Update Webhook

PUT /webhooks/{webhook_id}/
Updates an existing webhook endpoint configuration.
{
  "url": "https://new-endpoint.com/webhook",
  "auth_method": "API_TOKEN",
  "credentials": {
    "header": "X-API-Key",
    "token": "new_api_key"
  }
}

Delete Webhook

DELETE /webhooks/{webhook_id}/
Removes a webhook endpoint from your business configuration. The server will respond with a 204 No Content status, indicating the webhook has been successfully deleted and there is no further information to return.

Test Webhook

POST /webhooks/{webhook_id}/test/
Tests webhook endpoint connectivity and updates status accordingly.
{
  "id": 123,
  "url": "https://your-site.com/webhook",
  "status": "active",
  "auth_method": "BEARER",
  "credentials": {
    "token": "your_bearer_token"
  }
}

Webhook Configuration

The following tables give you a quick overview of the available webhook authentication methods and status values.

Authentication Methods

These are the available webhook authentication methods.
MethodDescriptionCredentials Format
BEARERBearer token authentication{"token": "your_bearer_token"}
API_TOKENCustom header with API key{"header": "X-API-Key", "token": "your_api_key"}
BASIC_AUTHHTTP Basic Authentication{"username": "user", "password": "pass"}
NONENo authentication{} (not recommended for production)

Webhook Status

These are the available webhook status values.
StatusDescription
activeWebhook is operational and receiving notifications
inactiveWebhook is configured but not receiving notifications
errorWebhook endpoint failed connectivity test

Webhook Events

Tonder sends webhook notifications for various transaction events:

Payment Events

  • Transaction status changes (pending → authorized → success)
  • Payment failures and declines
  • 3DS authentication completions
  • Cash payment confirmations (OXXO, SafetyPay)

Withdrawal Events

  • Withdrawal processing updates
  • Payout completions and failures
  • SPEI transfer confirmations
  • Debit card deposit completions

Webhook Payload Structure

All webhook notifications follow a consistent payload structure:
{
  "event_type": "transaction.status_changed",
  "event_id": "evt_123456789",
  "created_at": "2024-07-26T10:32:15Z",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "operation_type": "payment",
    "status": "success",
    "previous_status": "authorized",
    "amount": 150.00,
    "currency": "MXN",
    "merchant_reference": "order-789",
    "payment_method": "CARD",
    "customer": {
      "name": "Ana María Rodríguez",
      "email": "ana.rodriguez@email.com"
    },
    "completed_at": "2024-07-26T10:32:15Z",
    "authorization_code": "AUTH123456"
  }
}
Where:
FieldTypeDescription
event_typestringType of event that occurred
event_idstringUnique identifier for this webhook event
created_atstringISO 8601 timestamp when event occurred
dataobjectTransaction data and details
data.statusstringCurrent transaction status
data.previous_statusstringPrevious transaction status

Implementation Examples

The following examples show how to implement webhook handling in different programming languages.
from flask import Flask, request, jsonify
import json
import logging

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def handle_webhook():
    """Handle Tonder webhook notifications"""
    
    # Verify authentication based on your configured method
    if not verify_webhook_auth(request.headers):
        logging.warning("Webhook authentication failed")
        return jsonify({'error': 'Authentication failed'}), 401
    
    try:
        # Process webhook event
        event_data = request.get_json()
        result = process_webhook_event(event_data)
        
        if result:
            return 'OK', 200
        else:
            return jsonify({'error': 'Processing failed'}), 500
            
    except Exception as e:
        logging.error(f"Webhook processing error: {e}")
        return jsonify({'error': 'Processing failed'}), 500

def verify_webhook_auth(headers):
    """Verify webhook authentication"""
    
    # For BEARER: verify Authorization header
    auth_header = headers.get('Authorization')
    if auth_header and auth_header.startswith('Bearer '):
        token = auth_header[7:]
        return validate_bearer_token(token)
    
    # For API_TOKEN: verify custom header
    api_key = headers.get('X-API-Key')
    if api_key:
        return validate_api_key(api_key)
    
    # For BASIC_AUTH: verify basic authentication
    if auth_header and auth_header.startswith('Basic '):
        return validate_basic_auth(auth_header)
    
    return False

def process_webhook_event(event_data):
    """Process webhook event based on type"""
    
    event_type = event_data.get('event_type')
    transaction_data = event_data.get('data', {})
    
    transaction_id = transaction_data.get('id')
    status = transaction_data.get('status')
    operation_type = transaction_data.get('operation_type')
    
    logging.info(f"Processing webhook: {event_type} for {transaction_id}")
    
    if event_type == 'transaction.status_changed':
        return handle_status_change(transaction_data)
    else:
        logging.warning(f"Unknown event type: {event_type}")
        return True  # Return success for unknown events

def handle_status_change(transaction_data):
    """Handle transaction status change events"""
    
    transaction_id = transaction_data['id']
    new_status = transaction_data['status']
    previous_status = transaction_data.get('previous_status')
    operation_type = transaction_data['operation_type']
    
    try:
        if operation_type == 'payment':
            return handle_payment_status_change(transaction_data)
        elif operation_type == 'withdrawal':
            return handle_withdrawal_status_change(transaction_data)
        else:
            logging.warning(f"Unknown operation type: {operation_type}")
            return True
            
    except Exception as e:
        logging.error(f"Error handling status change for {transaction_id}: {e}")
        return False

def handle_payment_status_change(transaction_data):
    """Handle payment status changes"""
    
    transaction_id = transaction_data['id']
    status = transaction_data['status']
    merchant_reference = transaction_data.get('merchant_reference')
    
    if status == 'success':
        # Payment completed successfully
        update_order_status(merchant_reference, 'paid')
        send_confirmation_email(transaction_data)
        
    elif status == 'failed':
        # Payment failed
        update_order_status(merchant_reference, 'payment_failed')
        send_failure_notification(transaction_data)
        
    elif status == 'declined':
        # Payment was declined
        update_order_status(merchant_reference, 'payment_declined')
        send_decline_notification(transaction_data)
    
    return True

def handle_withdrawal_status_change(transaction_data):
    """Handle withdrawal status changes"""
    
    transaction_id = transaction_data['id']
    status = transaction_data['status']
    merchant_reference = transaction_data.get('merchant_reference')
    
    if status == 'success':
        # Withdrawal completed successfully
        update_payout_status(merchant_reference, 'completed')
        notify_beneficiary(transaction_data)
        
    elif status == 'failed':
        # Withdrawal failed
        update_payout_status(merchant_reference, 'failed')
        handle_payout_failure(transaction_data)
    
    return True

def validate_bearer_token(token):
    """Validate bearer token"""
    # Implement your token validation logic
    return token == "your_expected_bearer_token"

def validate_api_key(api_key):
    """Validate API key"""
    # Implement your API key validation logic
    return api_key == "your_expected_api_key"

def update_order_status(merchant_reference, status):
    """Update order status in your system"""
    # Implement your order status update logic
    pass

def send_confirmation_email(transaction_data):
    """Send payment confirmation email"""
    # Implement your email sending logic
    pass

if __name__ == '__main__':
    app.run(debug=True, port=5000)

Webhook Delivery & Retry Logic

Tonder’s webhook service ensures reliable delivery of webhook notifications through AWS SQS-based retry mechanisms and comprehensive error handling. The following are the retry configuration for webhook delivery:
SettingValueDescription
Maximum retry attempts3Each webhook delivery will be tried up to 3 times
Processing timeout30 seconds per attemptEach delivery attempt has a 30-second processing window
Visibility timeout60 seconds between retries60 seconds wait before a failed attempt is retried
Total retry windowUp to 3 minutes (3 × 60 seconds)Maximum time spent retrying a single webhook
Message retention14 days in the primary queueMessages are kept for up to 14 days if not delivered

Retry Behavior

When a webhook delivery fails, the system automatically:
  1. Logs the failure with detailed error information and attempt tracking
  2. Returns the message to the SQS queue for retry
  3. Waits 60 seconds before the next retry attempt becomes available
  4. Repeats up to 3 times total

Failure Handling

These are the reasons why a webhook delivery is considered to have failed straight away:
  • Network connection errors
  • HTTP timeout (30 seconds)
  • Non-2xx HTTP response codes
  • Invalid webhook endpoints

Success Criteria

A webhook delivery is considered successful when:
  • HTTP response status is 2xx (200-299)
  • Response received within 30 seconds
  • No network or connection errors

Best Practices for Webhook Endpoints

Below are some best practices to help you build reliable, secure, and robust webhook endpoints.

Testing Webhooks

There are two main approaches to testing webhook integration: local development testing and production environment testing. Use this code example to create a test endpoint that works for both local and production testing approaches:
Test Webhook Endpoint
@app.route('/webhook/test', methods=['POST'])
def test_webhook():
    """Simple test endpoint for webhook verification"""
    
    event_data = request.get_json()
    print(f"Received webhook: {json.dumps(event_data, indent=2)}")
    
    # Log the event
    logging.info(f"Test webhook received: {event_data.get('event_type')}")
    
    return 'OK', 200

Local Development Testing

Use this approach during development to test webhook integration:
  1. Use ngrok for local testing: ngrok http 5000
  2. Configure webhook URL with the ngrok URL
  3. Process test transactions to trigger webhooks
  4. Verify payload structure and authentication

Production Environment Testing

Use this approach to test webhooks in a production-like environment:
  1. Test connectivity using the test webhook endpoint
  2. Verify authentication works correctly
  3. Check retry behaviour by temporarily returning errors
  4. Monitor webhook logs for delivery success

Monitoring and Observability

The webhook service provides comprehensive logging for:
  • Individual delivery attempts with timing metrics
  • Retry attempt tracking (attempt_number, max_attempts, will_retry)
  • Performance metrics (request duration, processing time)
  • Failure analysis with exception details
To help you make sure everything’s running smoothly, follow this monitoring checklist:
  • Monitor webhook delivery success rates
  • Set up alerts for repeated failures
  • Track webhook processing times
  • Monitor endpoint uptime and availability
  • Review failed webhook logs regularly

Troubleshooting

These are the most common issues you might encounter and how to resolve them:
IssueCauseSolution
401 Authentication errorsInvalid credentialsVerify webhook authentication settings
404 Endpoint not foundIncorrect URLCheck webhook URL configuration
Timeout errorsSlow processingOptimize webhook processing time
500 Server errorsProcessing failuresCheck webhook endpoint logs
To help you troubleshoot issues, follow these debugging steps:
  1. Check webhook configuration - Verify URL and authentication
  2. Test endpoint manually - Use curl or Postman to test
  3. Review logs - Check both Tonder and your endpoint logs
  4. Verify network connectivity - Ensure endpoint is accessible
  5. Test with simple payload - Use webhook test endpoint

Next Steps