CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-twilio

Twilio API client and TwiML generator for comprehensive telecommunications services

Overview
Eval results
Files

webhooks-validation.mddocs/

Webhooks & Validation

Request signature validation for securing webhook endpoints and ensuring requests originate from Twilio. Provides utilities for computing and validating request signatures.

Capabilities

Request Validation

Validate incoming webhooks to ensure they originated from Twilio using HMAC-SHA1 signatures.

class RequestValidator:
    """Webhook request signature validator"""
    
    def __init__(self, auth_token: str):
        """
        Initialize validator with auth token.
        
        Args:
            auth_token (str): Twilio Account Auth Token
        """
    
    def validate(
        self,
        uri: str,
        params: dict,
        signature: str
    ) -> bool:
        """
        Validate webhook request signature.
        
        Args:
            uri (str): Full request URI including query string
            params (dict): POST body parameters or query parameters
            signature (str): X-Twilio-Signature header value
            
        Returns:
            bool: True if signature is valid
        """
    
    def compute_signature(
        self,
        uri: str,
        params: dict
    ) -> str:
        """
        Compute expected signature for request.
        
        Args:
            uri (str): Full request URI
            params (dict): Request parameters
            
        Returns:
            str: Expected signature string
        """
    
    def compute_body_hash(self, body: str) -> str:
        """
        Compute SHA256 hash of request body.
        
        Args:
            body (str): Raw request body
            
        Returns:
            str: Base64-encoded body hash
        """

Utility Functions

Helper functions for request validation and URI manipulation.

def compare(string1: str, string2: str) -> bool:
    """
    Timing-safe string comparison to prevent timing attacks.
    
    Args:
        string1 (str): First string
        string2 (str): Second string
        
    Returns:
        bool: True if strings are equal
    """

def remove_port(uri: str) -> str:
    """
    Remove port number from URI if it's default port.
    
    Args:
        uri (str): URI with potential port
        
    Returns:
        str: URI with default port removed
    """

def add_port(uri: str) -> str:
    """
    Add default port to URI if missing.
    
    Args:
        uri (str): URI potentially missing port
        
    Returns:
        str: URI with explicit port
    """

Webhook Validation Examples

Basic Validation

Validate incoming webhook requests in web frameworks.

from twilio.request_validator import RequestValidator
from flask import Flask, request

app = Flask(__name__)
validator = RequestValidator('your_auth_token')

@app.route('/webhook', methods=['POST'])
def webhook_handler():
    # Get signature from headers
    signature = request.headers.get('X-Twilio-Signature', '')
    
    # Get full URL (important: include protocol and port)
    url = request.url
    
    # Get POST parameters
    params = request.form.to_dict()
    
    # Validate signature
    if not validator.validate(url, params, signature):
        return 'Forbidden', 403
    
    # Process validated webhook
    from_number = params.get('From')
    body = params.get('Body')
    
    print(f"Valid webhook from {from_number}: {body}")
    return 'OK', 200

Django Validation

from django.http import HttpResponse, HttpResponseForbidden
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from twilio.request_validator import RequestValidator
import json

validator = RequestValidator('your_auth_token')

@csrf_exempt
@require_POST
def twilio_webhook(request):
    # Get signature
    signature = request.META.get('HTTP_X_TWILIO_SIGNATURE', '')
    
    # Build full URL
    url = request.build_absolute_uri()
    
    # Get parameters
    params = {}
    for key, value in request.POST.items():
        params[key] = value
    
    # Validate
    if not validator.validate(url, params, signature):
        return HttpResponseForbidden('Invalid signature')
    
    # Handle webhook
    call_sid = params.get('CallSid')
    call_status = params.get('CallStatus')
    
    return HttpResponse('OK')

Manual Signature Computation

from twilio.request_validator import RequestValidator

validator = RequestValidator('your_auth_token')

# Compute signature manually
uri = 'https://example.com/webhook'
params = {
    'From': '+15551234567',
    'To': '+15559876543',
    'Body': 'Hello World',
    'MessageSid': 'SMxxxxx'
}

expected_signature = validator.compute_signature(uri, params)
print(f"Expected signature: {expected_signature}")

# Validate against actual signature
actual_signature = 'signature_from_header'
is_valid = validator.validate(uri, params, actual_signature)
print(f"Valid: {is_valid}")

Handling Different Content Types

from twilio.request_validator import RequestValidator
from flask import Flask, request
import json

app = Flask(__name__)
validator = RequestValidator('your_auth_token')

@app.route('/webhook', methods=['POST'])
def webhook_handler():
    signature = request.headers.get('X-Twilio-Signature', '')
    url = request.url
    
    # Handle form-encoded data (default)
    if request.content_type == 'application/x-www-form-urlencoded':
        params = request.form.to_dict()
        
    # Handle JSON data (for some webhook types)
    elif request.content_type == 'application/json':
        # For JSON, validate against raw body
        body = request.get_data(as_text=True)
        body_hash = validator.compute_body_hash(body)
        
        # Add body hash to empty params for validation
        params = {'bodySHA256': body_hash}
        
    else:
        return 'Unsupported content type', 400
    
    if not validator.validate(url, params, signature):
        return 'Forbidden', 403
    
    return 'OK', 200

Debugging Validation Issues

from twilio.request_validator import RequestValidator

def debug_validation(uri, params, signature, auth_token):
    """Debug webhook validation issues"""
    validator = RequestValidator(auth_token)
    
    print(f"URI: {uri}")
    print(f"Params: {params}")
    print(f"Signature: {signature}")
    
    # Compute expected signature
    expected = validator.compute_signature(uri, params)
    print(f"Expected: {expected}")
    
    # Check if they match
    is_valid = validator.validate(uri, params, signature)
    print(f"Valid: {is_valid}")
    
    # Common issues to check
    if not is_valid:
        print("\nCommon issues to check:")
        print("1. Ensure URI includes protocol (https://)")
        print("2. Ensure URI includes correct port (if not 80/443)")
        print("3. Check for URL encoding differences")
        print("4. Verify auth token is correct")
        print("5. Check for proxy/load balancer URI changes")
        
        # Try with port manipulation
        uri_with_port = add_port(uri)
        uri_without_port = remove_port(uri)
        
        if validator.validate(uri_with_port, params, signature):
            print(f"✓ Valid with explicit port: {uri_with_port}")
        elif validator.validate(uri_without_port, params, signature):
            print(f"✓ Valid without port: {uri_without_port}")

# Usage
debug_validation(
    uri='https://example.com/webhook',
    params={'From': '+15551234567', 'Body': 'Test'},
    signature='signature_from_header',
    auth_token='your_auth_token'
)

Production Validation Patterns

from twilio.request_validator import RequestValidator
from functools import wraps
import os

# Initialize validator with environment variable
validator = RequestValidator(os.environ.get('TWILIO_AUTH_TOKEN'))

def validate_twilio_request(f):
    """Decorator for automatic webhook validation"""
    @wraps(f)
    def decorated_function(*args, **kwargs):
        from flask import request, abort
        
        signature = request.headers.get('X-Twilio-Signature', '')
        url = request.url
        params = request.form.to_dict()
        
        if not validator.validate(url, params, signature):
            abort(403)  # Forbidden
            
        return f(*args, **kwargs)
    return decorated_function

# Usage
@app.route('/voice-webhook', methods=['POST'])
@validate_twilio_request
def handle_voice():
    # This code only runs for valid Twilio requests
    from twilio.twiml.voice_response import VoiceResponse
    
    response = VoiceResponse()
    response.say("Hello from validated webhook!")
    return str(response)

@app.route('/sms-webhook', methods=['POST']) 
@validate_twilio_request
def handle_sms():
    from twilio.twiml.messaging_response import MessagingResponse
    
    body = request.form.get('Body', '').strip()
    
    response = MessagingResponse()
    response.message(f"You said: {body}")
    return str(response)

Security Best Practices

  1. Always validate signatures in production webhooks
  2. Use HTTPS for all webhook URLs
  3. Store auth tokens securely using environment variables
  4. Log validation failures for monitoring
  5. Handle URL encoding consistently across your application
  6. Consider rate limiting webhook endpoints
  7. Validate request origin using signatures, not IP addresses
  8. Use timing-safe comparison (provided by the validator)
  9. Test validation with Twilio's webhook testing tools
  10. Monitor webhook health and response times

Install with Tessl CLI

npx tessl i tessl/pypi-twilio

docs

advanced-services.md

authentication-jwt.md

core-communications.md

index.md

infrastructure.md

rest-client.md

twiml-generation.md

webhooks-validation.md

tile.json