0
# Webhook Validation
1
2
Webhook validation utilities provide security functions to verify that incoming HTTP requests to your application originated from Twilio servers. This prevents unauthorized access and ensures request integrity through cryptographic signature validation.
3
4
## Capabilities
5
6
### Request Signature Validation
7
8
Core functions for validating Twilio webhook request signatures using HMAC-SHA1 cryptographic verification.
9
10
```typescript { .api }
11
/**
12
* Validate that an incoming request originated from Twilio
13
* @param authToken - Your Twilio auth token from the console
14
* @param twilioSignature - Value of X-Twilio-Signature header from the request
15
* @param url - The full webhook URL you configured with Twilio (including query string)
16
* @param params - The POST parameters sent with the request
17
* @returns true if request is valid, false otherwise
18
*/
19
function validateRequest(
20
authToken: string,
21
twilioSignature: string,
22
url: string,
23
params: Record<string, any>
24
): boolean;
25
26
/**
27
* Validate request signature including body hash verification
28
* @param authToken - Your Twilio auth token
29
* @param twilioSignature - Value of X-Twilio-Signature header
30
* @param url - The full webhook URL (must include bodySHA256 parameter)
31
* @param body - The raw request body as a string
32
* @returns true if both signature and body hash are valid
33
*/
34
function validateRequestWithBody(
35
authToken: string,
36
twilioSignature: string,
37
url: string,
38
body: string
39
): boolean;
40
41
/**
42
* Validate incoming request using a request object (Express.js compatible)
43
* @param request - HTTP request object with headers, body, and URL properties
44
* @param authToken - Your Twilio auth token
45
* @param opts - Optional configuration for URL construction
46
* @returns true if request is valid
47
*/
48
function validateIncomingRequest(
49
request: Request,
50
authToken: string,
51
opts?: RequestValidatorOptions
52
): boolean;
53
54
/**
55
* Express.js specific request validation (alias for validateIncomingRequest)
56
* @param request - Express request object
57
* @param authToken - Your Twilio auth token
58
* @param opts - Optional validation configuration
59
* @returns true if request is valid
60
*/
61
function validateExpressRequest(
62
request: Request,
63
authToken: string,
64
opts?: RequestValidatorOptions
65
): boolean;
66
```
67
68
**Usage Examples:**
69
70
```typescript
71
import TwilioSDK from "twilio";
72
73
// Access webhook validation functions
74
const { validateRequest, validateRequestWithBody } = TwilioSDK;
75
76
// Basic webhook validation
77
app.post("/webhook", (req, res) => {
78
const signature = req.headers["x-twilio-signature"];
79
const url = "https://myapp.com/webhook";
80
const params = req.body;
81
82
const isValid = validateRequest(
83
process.env.TWILIO_AUTH_TOKEN,
84
signature,
85
url,
86
params
87
);
88
89
if (!isValid) {
90
return res.status(403).send("Invalid signature");
91
}
92
93
// Process webhook
94
res.send("OK");
95
});
96
97
// Validation with body hash (for high-security webhooks)
98
app.post("/secure-webhook", (req, res) => {
99
const signature = req.headers["x-twilio-signature"];
100
const url = `https://myapp.com/secure-webhook?bodySHA256=${req.query.bodySHA256}`;
101
const body = req.rawBody;
102
103
const isValid = validateRequestWithBody(
104
process.env.TWILIO_AUTH_TOKEN,
105
signature,
106
url,
107
body
108
);
109
110
if (isValid) {
111
// Process secure webhook
112
res.send("OK");
113
} else {
114
res.status(403).send("Invalid signature or body");
115
}
116
});
117
```
118
119
### Express.js Middleware
120
121
Pre-built Express.js middleware for automatic webhook validation with comprehensive error handling.
122
123
```typescript { .api }
124
/**
125
* Express.js middleware for Twilio webhook validation and TwiML response helpers
126
* @param opts - Configuration options or auth token string
127
* @param authToken - Alternative way to provide auth token
128
* @returns Express middleware function
129
*/
130
function webhook(
131
opts?: string | WebhookOptions,
132
authToken?: string | WebhookOptions
133
): (req: any, res: any, next: Function) => void;
134
135
interface WebhookOptions {
136
/** Whether to validate the request (default: true) */
137
validate?: boolean;
138
/** Enable TwiML response helpers (default: true) */
139
includeHelpers?: boolean;
140
/** Full webhook URL (overrides host/protocol) */
141
url?: string;
142
/** Manually specify hostname for validation */
143
host?: string;
144
/** Manually specify protocol for validation */
145
protocol?: string;
146
/** Auth token for validation */
147
authToken?: string;
148
}
149
```
150
151
**Usage Examples:**
152
153
```typescript
154
import express from "express";
155
import TwilioSDK from "twilio";
156
157
const app = express();
158
const { webhook } = TwilioSDK;
159
160
// Basic webhook middleware with environment variable auth token
161
app.use("/webhooks", webhook());
162
163
// Webhook middleware with explicit auth token
164
app.use("/webhooks", webhook(process.env.TWILIO_AUTH_TOKEN));
165
166
// Webhook middleware with custom configuration
167
app.use("/webhooks", webhook({
168
validate: true,
169
host: "myapp.ngrok.io",
170
protocol: "https",
171
authToken: process.env.TWILIO_AUTH_TOKEN
172
}));
173
174
// Webhook middleware without validation (for development)
175
app.use("/dev-webhooks", webhook({ validate: false }));
176
177
// Route handler after middleware
178
app.post("/webhooks/voice", (req, res) => {
179
// Request is already validated by middleware
180
const response = new VoiceResponse();
181
response.say("Hello from validated webhook!");
182
res.type("text/xml").send(response.toString());
183
});
184
```
185
186
### Request Validation Configuration
187
188
Configuration options for fine-tuning webhook validation behavior.
189
190
```typescript { .api }
191
interface RequestValidatorOptions {
192
/** Full webhook URL with query string (overrides host/protocol) */
193
url?: string;
194
/** Hostname used by Twilio in webhook configuration */
195
host?: string;
196
/** Protocol used by Twilio in webhook configuration */
197
protocol?: string;
198
}
199
200
interface Request {
201
/** Request protocol (http/https) */
202
protocol: string;
203
/** Get header value by name */
204
header(name: string): string | undefined;
205
/** All request headers */
206
headers: IncomingHttpHeaders;
207
/** Original request URL with query string */
208
originalUrl: string;
209
/** Raw request body (for body hash validation) */
210
rawBody?: any;
211
/** Parsed request body parameters */
212
body: any;
213
}
214
```
215
216
### Signature Generation Utilities
217
218
Low-level utilities for generating and comparing webhook signatures.
219
220
```typescript { .api }
221
/**
222
* Generate the expected signature for a given request
223
* @param authToken - Your Twilio auth token
224
* @param url - The full webhook URL with query parameters
225
* @param params - The request parameters
226
* @returns Expected signature string (base64 encoded)
227
*/
228
function getExpectedTwilioSignature(
229
authToken: string,
230
url: string,
231
params: Record<string, any>
232
): string;
233
234
/**
235
* Generate the expected SHA256 hash for a request body
236
* @param body - The plain-text request body
237
* @returns Expected body hash (hex encoded)
238
*/
239
function getExpectedBodyHash(body: string): string;
240
241
/**
242
* Validate request body against provided hash
243
* @param body - The request body string
244
* @param bodyHash - The hash to validate against
245
* @returns true if body hash matches
246
*/
247
function validateBody(
248
body: string,
249
bodyHash: string | Buffer | any[]
250
): boolean;
251
```
252
253
**Usage Examples:**
254
255
```typescript
256
// Manual signature verification
257
const expectedSignature = getExpectedTwilioSignature(
258
authToken,
259
"https://myapp.com/webhook?foo=bar",
260
{ From: "+1234567890", Body: "Hello" }
261
);
262
263
const providedSignature = req.headers["x-twilio-signature"];
264
const isValid = expectedSignature === providedSignature;
265
266
// Body hash verification
267
const bodyHash = getExpectedBodyHash(req.rawBody);
268
const isBodyValid = validateBody(req.rawBody, req.query.bodySHA256);
269
```
270
271
### Advanced Validation Scenarios
272
273
Handle complex webhook validation scenarios including proxy servers, load balancers, and custom URL configurations.
274
275
**Usage Examples:**
276
277
```typescript
278
// Validation behind reverse proxy
279
app.post("/webhook", (req, res) => {
280
const isValid = validateIncomingRequest(req, authToken, {
281
protocol: "https", // Force HTTPS even if proxy forwards HTTP
282
host: "myapp.com" // Use external hostname
283
});
284
});
285
286
// Validation with custom webhook URL
287
app.post("/webhook", (req, res) => {
288
const isValid = validateIncomingRequest(req, authToken, {
289
url: "https://myapp.com/webhook?custom=param"
290
});
291
});
292
293
// Multiple validation attempts for different URL formats
294
function validateWebhook(req, authToken) {
295
const signature = req.headers["x-twilio-signature"];
296
297
// Try different URL variations that Twilio might use
298
const urls = [
299
`https://${req.headers.host}${req.originalUrl}`,
300
`https://${req.headers.host}:443${req.originalUrl}`,
301
`http://${req.headers.host}${req.originalUrl}`,
302
`http://${req.headers.host}:80${req.originalUrl}`
303
];
304
305
return urls.some(url =>
306
validateRequest(authToken, signature, url, req.body)
307
);
308
}
309
```
310
311
### Security Best Practices
312
313
Important security considerations when implementing webhook validation.
314
315
**Usage Examples:**
316
317
```typescript
318
// Always validate in production
319
const isProduction = process.env.NODE_ENV === "production";
320
321
app.use("/webhooks", webhook({
322
validate: isProduction, // Only skip validation in development
323
authToken: process.env.TWILIO_AUTH_TOKEN
324
}));
325
326
// Secure error handling
327
app.post("/webhook", (req, res) => {
328
try {
329
const isValid = validateRequest(/* ... */);
330
331
if (!isValid) {
332
// Log failed validation attempts
333
console.warn("Invalid webhook signature", {
334
ip: req.ip,
335
userAgent: req.headers["user-agent"],
336
timestamp: new Date().toISOString()
337
});
338
339
return res.status(403).send("Forbidden");
340
}
341
342
// Process valid webhook...
343
344
} catch (error) {
345
console.error("Webhook validation error:", error);
346
res.status(500).send("Internal Server Error");
347
}
348
});
349
350
// Rate limiting validation failures
351
const rateLimit = require("express-rate-limit");
352
353
const validationFailureLimit = rateLimit({
354
windowMs: 15 * 60 * 1000, // 15 minutes
355
max: 5, // limit each IP to 5 failed validations per windowMs
356
skip: (req) => {
357
// Only count failed validations
358
const isValid = validateIncomingRequest(req, authToken);
359
return isValid;
360
},
361
message: "Too many invalid webhook attempts"
362
});
363
364
app.use("/webhooks", validationFailureLimit);
365
```
366
367
## Types
368
369
```typescript { .api }
370
interface IncomingHttpHeaders {
371
[key: string]: string | string[] | undefined;
372
}
373
374
type ValidationResult = boolean;
375
376
interface SignatureComponents {
377
/** The webhook URL used for signature generation */
378
url: string;
379
/** The request parameters included in signature */
380
params: Record<string, any>;
381
/** The computed signature hash */
382
signature: string;
383
}
384
```