or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

credential-providers.mdindex.mdjwt.mdrest-client.mdtwiml.mdwebhooks.md

webhooks.mddocs/

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

```