or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-services.mdauthentication-jwt.mdcore-communications.mdindex.mdinfrastructure.mdrest-client.mdtwiml-generation.mdwebhooks-validation.md

webhooks-validation.mddocs/

0

# Webhooks & Validation

1

2

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

3

4

## Capabilities

5

6

### Request Validation

7

8

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

9

10

```python { .api }

11

class RequestValidator:

12

"""Webhook request signature validator"""

13

14

def __init__(self, auth_token: str):

15

"""

16

Initialize validator with auth token.

17

18

Args:

19

auth_token (str): Twilio Account Auth Token

20

"""

21

22

def validate(

23

self,

24

uri: str,

25

params: dict,

26

signature: str

27

) -> bool:

28

"""

29

Validate webhook request signature.

30

31

Args:

32

uri (str): Full request URI including query string

33

params (dict): POST body parameters or query parameters

34

signature (str): X-Twilio-Signature header value

35

36

Returns:

37

bool: True if signature is valid

38

"""

39

40

def compute_signature(

41

self,

42

uri: str,

43

params: dict

44

) -> str:

45

"""

46

Compute expected signature for request.

47

48

Args:

49

uri (str): Full request URI

50

params (dict): Request parameters

51

52

Returns:

53

str: Expected signature string

54

"""

55

56

def compute_body_hash(self, body: str) -> str:

57

"""

58

Compute SHA256 hash of request body.

59

60

Args:

61

body (str): Raw request body

62

63

Returns:

64

str: Base64-encoded body hash

65

"""

66

```

67

68

### Utility Functions

69

70

Helper functions for request validation and URI manipulation.

71

72

```python { .api }

73

def compare(string1: str, string2: str) -> bool:

74

"""

75

Timing-safe string comparison to prevent timing attacks.

76

77

Args:

78

string1 (str): First string

79

string2 (str): Second string

80

81

Returns:

82

bool: True if strings are equal

83

"""

84

85

def remove_port(uri: str) -> str:

86

"""

87

Remove port number from URI if it's default port.

88

89

Args:

90

uri (str): URI with potential port

91

92

Returns:

93

str: URI with default port removed

94

"""

95

96

def add_port(uri: str) -> str:

97

"""

98

Add default port to URI if missing.

99

100

Args:

101

uri (str): URI potentially missing port

102

103

Returns:

104

str: URI with explicit port

105

"""

106

```

107

108

## Webhook Validation Examples

109

110

### Basic Validation

111

112

Validate incoming webhook requests in web frameworks.

113

114

```python

115

from twilio.request_validator import RequestValidator

116

from flask import Flask, request

117

118

app = Flask(__name__)

119

validator = RequestValidator('your_auth_token')

120

121

@app.route('/webhook', methods=['POST'])

122

def webhook_handler():

123

# Get signature from headers

124

signature = request.headers.get('X-Twilio-Signature', '')

125

126

# Get full URL (important: include protocol and port)

127

url = request.url

128

129

# Get POST parameters

130

params = request.form.to_dict()

131

132

# Validate signature

133

if not validator.validate(url, params, signature):

134

return 'Forbidden', 403

135

136

# Process validated webhook

137

from_number = params.get('From')

138

body = params.get('Body')

139

140

print(f"Valid webhook from {from_number}: {body}")

141

return 'OK', 200

142

```

143

144

### Django Validation

145

146

```python

147

from django.http import HttpResponse, HttpResponseForbidden

148

from django.views.decorators.csrf import csrf_exempt

149

from django.views.decorators.http import require_POST

150

from twilio.request_validator import RequestValidator

151

import json

152

153

validator = RequestValidator('your_auth_token')

154

155

@csrf_exempt

156

@require_POST

157

def twilio_webhook(request):

158

# Get signature

159

signature = request.META.get('HTTP_X_TWILIO_SIGNATURE', '')

160

161

# Build full URL

162

url = request.build_absolute_uri()

163

164

# Get parameters

165

params = {}

166

for key, value in request.POST.items():

167

params[key] = value

168

169

# Validate

170

if not validator.validate(url, params, signature):

171

return HttpResponseForbidden('Invalid signature')

172

173

# Handle webhook

174

call_sid = params.get('CallSid')

175

call_status = params.get('CallStatus')

176

177

return HttpResponse('OK')

178

```

179

180

### Manual Signature Computation

181

182

```python

183

from twilio.request_validator import RequestValidator

184

185

validator = RequestValidator('your_auth_token')

186

187

# Compute signature manually

188

uri = 'https://example.com/webhook'

189

params = {

190

'From': '+15551234567',

191

'To': '+15559876543',

192

'Body': 'Hello World',

193

'MessageSid': 'SMxxxxx'

194

}

195

196

expected_signature = validator.compute_signature(uri, params)

197

print(f"Expected signature: {expected_signature}")

198

199

# Validate against actual signature

200

actual_signature = 'signature_from_header'

201

is_valid = validator.validate(uri, params, actual_signature)

202

print(f"Valid: {is_valid}")

203

```

204

205

### Handling Different Content Types

206

207

```python

208

from twilio.request_validator import RequestValidator

209

from flask import Flask, request

210

import json

211

212

app = Flask(__name__)

213

validator = RequestValidator('your_auth_token')

214

215

@app.route('/webhook', methods=['POST'])

216

def webhook_handler():

217

signature = request.headers.get('X-Twilio-Signature', '')

218

url = request.url

219

220

# Handle form-encoded data (default)

221

if request.content_type == 'application/x-www-form-urlencoded':

222

params = request.form.to_dict()

223

224

# Handle JSON data (for some webhook types)

225

elif request.content_type == 'application/json':

226

# For JSON, validate against raw body

227

body = request.get_data(as_text=True)

228

body_hash = validator.compute_body_hash(body)

229

230

# Add body hash to empty params for validation

231

params = {'bodySHA256': body_hash}

232

233

else:

234

return 'Unsupported content type', 400

235

236

if not validator.validate(url, params, signature):

237

return 'Forbidden', 403

238

239

return 'OK', 200

240

```

241

242

### Debugging Validation Issues

243

244

```python

245

from twilio.request_validator import RequestValidator

246

247

def debug_validation(uri, params, signature, auth_token):

248

"""Debug webhook validation issues"""

249

validator = RequestValidator(auth_token)

250

251

print(f"URI: {uri}")

252

print(f"Params: {params}")

253

print(f"Signature: {signature}")

254

255

# Compute expected signature

256

expected = validator.compute_signature(uri, params)

257

print(f"Expected: {expected}")

258

259

# Check if they match

260

is_valid = validator.validate(uri, params, signature)

261

print(f"Valid: {is_valid}")

262

263

# Common issues to check

264

if not is_valid:

265

print("\nCommon issues to check:")

266

print("1. Ensure URI includes protocol (https://)")

267

print("2. Ensure URI includes correct port (if not 80/443)")

268

print("3. Check for URL encoding differences")

269

print("4. Verify auth token is correct")

270

print("5. Check for proxy/load balancer URI changes")

271

272

# Try with port manipulation

273

uri_with_port = add_port(uri)

274

uri_without_port = remove_port(uri)

275

276

if validator.validate(uri_with_port, params, signature):

277

print(f"✓ Valid with explicit port: {uri_with_port}")

278

elif validator.validate(uri_without_port, params, signature):

279

print(f"✓ Valid without port: {uri_without_port}")

280

281

# Usage

282

debug_validation(

283

uri='https://example.com/webhook',

284

params={'From': '+15551234567', 'Body': 'Test'},

285

signature='signature_from_header',

286

auth_token='your_auth_token'

287

)

288

```

289

290

### Production Validation Patterns

291

292

```python

293

from twilio.request_validator import RequestValidator

294

from functools import wraps

295

import os

296

297

# Initialize validator with environment variable

298

validator = RequestValidator(os.environ.get('TWILIO_AUTH_TOKEN'))

299

300

def validate_twilio_request(f):

301

"""Decorator for automatic webhook validation"""

302

@wraps(f)

303

def decorated_function(*args, **kwargs):

304

from flask import request, abort

305

306

signature = request.headers.get('X-Twilio-Signature', '')

307

url = request.url

308

params = request.form.to_dict()

309

310

if not validator.validate(url, params, signature):

311

abort(403) # Forbidden

312

313

return f(*args, **kwargs)

314

return decorated_function

315

316

# Usage

317

@app.route('/voice-webhook', methods=['POST'])

318

@validate_twilio_request

319

def handle_voice():

320

# This code only runs for valid Twilio requests

321

from twilio.twiml.voice_response import VoiceResponse

322

323

response = VoiceResponse()

324

response.say("Hello from validated webhook!")

325

return str(response)

326

327

@app.route('/sms-webhook', methods=['POST'])

328

@validate_twilio_request

329

def handle_sms():

330

from twilio.twiml.messaging_response import MessagingResponse

331

332

body = request.form.get('Body', '').strip()

333

334

response = MessagingResponse()

335

response.message(f"You said: {body}")

336

return str(response)

337

```

338

339

## Security Best Practices

340

341

1. **Always validate signatures** in production webhooks

342

2. **Use HTTPS** for all webhook URLs

343

3. **Store auth tokens securely** using environment variables

344

4. **Log validation failures** for monitoring

345

5. **Handle URL encoding consistently** across your application

346

6. **Consider rate limiting** webhook endpoints

347

7. **Validate request origin** using signatures, not IP addresses

348

8. **Use timing-safe comparison** (provided by the validator)

349

9. **Test validation** with Twilio's webhook testing tools

350

10. **Monitor webhook health** and response times