or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-features.mdcore-application.mdindex.mdmiddleware.mdreceivers.mdrequest-verification.md

request-verification.mddocs/

0

# Request Verification

1

2

Slack Bolt provides request signature verification utilities to ensure requests are authentic and come from Slack. This is essential for security in production Slack applications.

3

4

## Capabilities

5

6

### Request Verification Functions

7

8

Verify that incoming requests are authentically from Slack using request signatures.

9

10

```typescript { .api }

11

/**

12

* Verify Slack request signature using HMAC-SHA256

13

* @param options - Verification options including signing secret and request data

14

* @throws Error if verification fails or request is invalid

15

*/

16

function verifySlackRequest(options: RequestVerificationOptions): void;

17

18

/**

19

* Check if Slack request signature is valid (non-throwing version)

20

* @param options - Verification options including signing secret and request data

21

* @returns True if the request signature is valid, false otherwise

22

*/

23

function isValidSlackRequest(options: RequestVerificationOptions): boolean;

24

25

interface RequestVerificationOptions {

26

/** Signing secret from your Slack app's Basic Information page */

27

signingSecret: string;

28

/** Raw request body as a string (not parsed JSON) */

29

body: string;

30

/** Request headers object containing X-Slack-Signature and X-Slack-Request-Timestamp */

31

headers: { [key: string]: string | string[] | undefined };

32

}

33

```

34

35

**Usage Examples:**

36

37

```typescript

38

import { verifySlackRequest, isValidSlackRequest } from "@slack/bolt";

39

import express from "express";

40

import { createServer } from "http";

41

42

// Express middleware for request verification

43

function verifySlackSignature(req: express.Request, res: express.Response, next: express.NextFunction) {

44

const signingSecret = process.env.SLACK_SIGNING_SECRET!;

45

46

try {

47

verifySlackRequest({

48

signingSecret,

49

body: req.body,

50

headers: req.headers

51

});

52

53

// If no error thrown, request is valid

54

next();

55

} catch (error) {

56

console.error("Signature verification failed:", error);

57

res.status(401).send("Unauthorized");

58

}

59

}

60

61

// Using with custom HTTP server

62

const server = createServer(async (req, res) => {

63

if (req.method === "POST" && req.url === "/slack/events") {

64

let body = "";

65

66

req.on("data", (chunk) => {

67

body += chunk.toString();

68

});

69

70

req.on("end", () => {

71

const isValid = isValidSlackRequest({

72

signingSecret: process.env.SLACK_SIGNING_SECRET!,

73

body,

74

headers: req.headers

75

});

76

77

if (!isValid) {

78

res.writeHead(401);

79

res.end("Unauthorized");

80

return;

81

}

82

83

// Process validated request

84

const event = JSON.parse(body);

85

// Handle event...

86

87

res.writeHead(200);

88

res.end("OK");

89

});

90

}

91

});

92

93

// Manual verification example

94

async function handleWebhook(requestBody: string, headers: Record<string, string>) {

95

const signingSecret = process.env.SLACK_SIGNING_SECRET!;

96

97

// Use non-throwing version for graceful handling

98

const isValid = isValidSlackRequest({

99

signingSecret,

100

body: requestBody,

101

headers

102

});

103

104

if (!isValid) {

105

console.warn("Received invalid request signature");

106

return { status: 401, body: "Unauthorized" };

107

}

108

109

// Process the verified request

110

const payload = JSON.parse(requestBody);

111

return await processSlackEvent(payload);

112

}

113

```

114

115

### HTTP Module Error Handling

116

117

Error handling interfaces and utilities for HTTP request processing.

118

119

```typescript { .api }

120

interface ReceiverDispatchErrorHandlerArgs {

121

/** Error that occurred during request dispatch */

122

error: Error | CodedError;

123

/** Logger instance for error reporting */

124

logger: Logger;

125

/** Incoming HTTP request */

126

request: IncomingMessage;

127

/** HTTP response object */

128

response: ServerResponse;

129

}

130

131

interface ReceiverProcessEventErrorHandlerArgs {

132

/** Error that occurred during event processing */

133

error: Error | CodedError;

134

/** Logger instance for error reporting */

135

logger: Logger;

136

/** Incoming HTTP request */

137

request: IncomingMessage;

138

/** HTTP response object */

139

response: ServerResponse;

140

/** Stored response data if any */

141

storedResponse: any;

142

}

143

144

interface ReceiverUnhandledRequestHandlerArgs {

145

/** Logger instance for logging unhandled requests */

146

logger: Logger;

147

/** Incoming HTTP request */

148

request: IncomingMessage;

149

/** HTTP response object */

150

response: ServerResponse;

151

}

152

```

153

154

### Buffered Request Handling

155

156

Enhanced request handling with buffered body content.

157

158

```typescript { .api }

159

/**

160

* Extended IncomingMessage with buffered body content

161

* Used internally by receivers for request processing

162

*/

163

class BufferedIncomingMessage extends IncomingMessage {

164

/** Request body as string */

165

body: string;

166

/** Raw request body as Buffer */

167

rawBody: Buffer;

168

}

169

170

/**

171

* HTTP response acknowledgment handler

172

* Provides structured way to acknowledge HTTP requests

173

*/

174

class HTTPResponseAck {

175

constructor(response: ServerResponse);

176

177

/** Send acknowledgment response to Slack */

178

ack(): Promise<void>;

179

}

180

```

181

182

### Socket Mode Error Handling

183

184

Error handling specifically for Socket Mode connections.

185

186

```typescript { .api }

187

interface SocketModeReceiverProcessEventErrorHandlerArgs {

188

/** Error that occurred during Socket Mode event processing */

189

error: Error | CodedError;

190

/** Logger instance for error reporting */

191

logger: Logger;

192

/** Socket Mode event data */

193

event: any;

194

}

195

196

/**

197

* Default error handler for Socket Mode event processing

198

* @param args - Error handler arguments

199

* @returns Promise resolving to boolean indicating if event should be retried

200

*/

201

function defaultProcessEventErrorHandler(

202

args: SocketModeReceiverProcessEventErrorHandlerArgs

203

): Promise<boolean>;

204

```

205

206

**Usage Examples:**

207

208

```typescript

209

import { App, HTTPReceiver, SocketModeReceiver } from "@slack/bolt";

210

211

// Custom HTTP receiver with error handling

212

const httpReceiver = new HTTPReceiver({

213

signingSecret: process.env.SLACK_SIGNING_SECRET!,

214

215

// Custom dispatch error handler

216

dispatchErrorHandler: async ({ error, logger, request, response }) => {

217

logger.error("Request dispatch failed", {

218

error: error.message,

219

url: request.url,

220

method: request.method

221

});

222

223

response.writeHead(500);

224

response.end("Internal Server Error");

225

},

226

227

// Custom event processing error handler

228

processEventErrorHandler: async ({ error, logger, request, response, storedResponse }) => {

229

logger.error("Event processing failed", {

230

error: error.message,

231

hasStoredResponse: !!storedResponse

232

});

233

234

// Return true to indicate the request should not be retried

235

return true;

236

},

237

238

// Custom unhandled request handler

239

unhandledRequestHandler: ({ logger, request, response }) => {

240

logger.warn("Unhandled request", {

241

url: request.url,

242

method: request.method,

243

headers: request.headers

244

});

245

246

response.writeHead(404);

247

response.end("Not Found");

248

}

249

});

250

251

// Socket Mode with custom error handling

252

const socketReceiver = new SocketModeReceiver({

253

appToken: process.env.SLACK_APP_TOKEN!,

254

clientOptions: {

255

// Socket Mode specific options

256

pingInterval: 30000,

257

}

258

});

259

260

const app = new App({

261

token: process.env.SLACK_BOT_TOKEN,

262

receiver: httpReceiver, // or socketReceiver

263

});

264

265

// Global error handler for the app

266

app.error(async (error) => {

267

console.error("Slack Bolt app error:", error);

268

269

// Send to monitoring service

270

if (error.code === "slack_bolt_receiver_authenticity_error") {

271

console.error("Authentication error - check signing secret");

272

} else if (error.code === "slack_bolt_authorization_error") {

273

console.error("Authorization error - check tokens and permissions");

274

}

275

});

276

```

277

278

### Custom Property Extraction

279

280

Extract custom properties from requests for enhanced context.

281

282

```typescript { .api }

283

/**

284

* Custom properties extractor function type

285

* Used to add custom data to request context from incoming requests

286

*/

287

type CustomPropertiesExtractor = (

288

request: BufferedIncomingMessage

289

) => StringIndexed;

290

```

291

292

**Usage Example:**

293

294

```typescript

295

import { App, ExpressReceiver } from "@slack/bolt";

296

297

const receiver = new ExpressReceiver({

298

signingSecret: process.env.SLACK_SIGNING_SECRET!,

299

300

// Extract custom properties from request

301

customPropertiesExtractor: (request) => {

302

return {

303

// Add request timing

304

requestReceivedAt: Date.now(),

305

306

// Add request metadata

307

userAgent: request.headers["user-agent"] as string,

308

309

// Add custom headers

310

customHeader: request.headers["x-custom-header"] as string,

311

312

// Add derived properties

313

isRetry: !!(request.headers["x-slack-retry-num"]),

314

retryNum: parseInt(request.headers["x-slack-retry-num"] as string) || 0

315

};

316

}

317

});

318

319

const app = new App({

320

receiver,

321

token: process.env.SLACK_BOT_TOKEN

322

});

323

324

// Use custom properties in middleware

325

app.use(async ({ context, logger, next }) => {

326

logger.info("Request metadata", {

327

receivedAt: context.requestReceivedAt,

328

userAgent: context.userAgent,

329

isRetry: context.isRetry,

330

retryNum: context.retryNum

331

});

332

333

await next();

334

});

335

```

336

337

## Security Best Practices

338

339

### Environment Configuration

340

341

```typescript

342

// Store sensitive values in environment variables

343

const config = {

344

signingSecret: process.env.SLACK_SIGNING_SECRET!, // Required for verification

345

botToken: process.env.SLACK_BOT_TOKEN!, // Required for API calls

346

appToken: process.env.SLACK_APP_TOKEN, // Required for Socket Mode

347

clientId: process.env.SLACK_CLIENT_ID, // Required for OAuth

348

clientSecret: process.env.SLACK_CLIENT_SECRET // Required for OAuth

349

};

350

351

// Validate required environment variables

352

if (!config.signingSecret) {

353

throw new Error("SLACK_SIGNING_SECRET environment variable is required");

354

}

355

```

356

357

### Request Validation

358

359

```typescript

360

import { App, isValidSlackRequest } from "@slack/bolt";

361

362

// Always validate requests in production

363

const app = new App({

364

token: process.env.SLACK_BOT_TOKEN,

365

signingSecret: process.env.SLACK_SIGNING_SECRET,

366

367

// Enable request verification (default: true)

368

// Only disable in development/testing environments

369

processBeforeResponse: false, // Set to true for serverless environments

370

});

371

372

// Manual validation example

373

function validateSlackRequest(body: string, headers: Record<string, string>): boolean {

374

return isValidSlackRequest({

375

signingSecret: process.env.SLACK_SIGNING_SECRET!,

376

body,

377

headers

378

});

379

}

380

```