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
```