A framework for building Slack apps, fast.
—
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.
Verify that incoming requests are authentically from Slack using request signatures.
/**
* Verify Slack request signature using HMAC-SHA256
* @param options - Verification options including signing secret and request data
* @throws Error if verification fails or request is invalid
*/
function verifySlackRequest(options: RequestVerificationOptions): void;
/**
* Check if Slack request signature is valid (non-throwing version)
* @param options - Verification options including signing secret and request data
* @returns True if the request signature is valid, false otherwise
*/
function isValidSlackRequest(options: RequestVerificationOptions): boolean;
interface RequestVerificationOptions {
/** Signing secret from your Slack app's Basic Information page */
signingSecret: string;
/** Raw request body as a string (not parsed JSON) */
body: string;
/** Request headers object containing X-Slack-Signature and X-Slack-Request-Timestamp */
headers: { [key: string]: string | string[] | undefined };
}Usage Examples:
import { verifySlackRequest, isValidSlackRequest } from "@slack/bolt";
import express from "express";
import { createServer } from "http";
// Express middleware for request verification
function verifySlackSignature(req: express.Request, res: express.Response, next: express.NextFunction) {
const signingSecret = process.env.SLACK_SIGNING_SECRET!;
try {
verifySlackRequest({
signingSecret,
body: req.body,
headers: req.headers
});
// If no error thrown, request is valid
next();
} catch (error) {
console.error("Signature verification failed:", error);
res.status(401).send("Unauthorized");
}
}
// Using with custom HTTP server
const server = createServer(async (req, res) => {
if (req.method === "POST" && req.url === "/slack/events") {
let body = "";
req.on("data", (chunk) => {
body += chunk.toString();
});
req.on("end", () => {
const isValid = isValidSlackRequest({
signingSecret: process.env.SLACK_SIGNING_SECRET!,
body,
headers: req.headers
});
if (!isValid) {
res.writeHead(401);
res.end("Unauthorized");
return;
}
// Process validated request
const event = JSON.parse(body);
// Handle event...
res.writeHead(200);
res.end("OK");
});
}
});
// Manual verification example
async function handleWebhook(requestBody: string, headers: Record<string, string>) {
const signingSecret = process.env.SLACK_SIGNING_SECRET!;
// Use non-throwing version for graceful handling
const isValid = isValidSlackRequest({
signingSecret,
body: requestBody,
headers
});
if (!isValid) {
console.warn("Received invalid request signature");
return { status: 401, body: "Unauthorized" };
}
// Process the verified request
const payload = JSON.parse(requestBody);
return await processSlackEvent(payload);
}Error handling interfaces and utilities for HTTP request processing.
interface ReceiverDispatchErrorHandlerArgs {
/** Error that occurred during request dispatch */
error: Error | CodedError;
/** Logger instance for error reporting */
logger: Logger;
/** Incoming HTTP request */
request: IncomingMessage;
/** HTTP response object */
response: ServerResponse;
}
interface ReceiverProcessEventErrorHandlerArgs {
/** Error that occurred during event processing */
error: Error | CodedError;
/** Logger instance for error reporting */
logger: Logger;
/** Incoming HTTP request */
request: IncomingMessage;
/** HTTP response object */
response: ServerResponse;
/** Stored response data if any */
storedResponse: any;
}
interface ReceiverUnhandledRequestHandlerArgs {
/** Logger instance for logging unhandled requests */
logger: Logger;
/** Incoming HTTP request */
request: IncomingMessage;
/** HTTP response object */
response: ServerResponse;
}Enhanced request handling with buffered body content.
/**
* Extended IncomingMessage with buffered body content
* Used internally by receivers for request processing
*/
class BufferedIncomingMessage extends IncomingMessage {
/** Request body as string */
body: string;
/** Raw request body as Buffer */
rawBody: Buffer;
}
/**
* HTTP response acknowledgment handler
* Provides structured way to acknowledge HTTP requests
*/
class HTTPResponseAck {
constructor(response: ServerResponse);
/** Send acknowledgment response to Slack */
ack(): Promise<void>;
}Error handling specifically for Socket Mode connections.
interface SocketModeReceiverProcessEventErrorHandlerArgs {
/** Error that occurred during Socket Mode event processing */
error: Error | CodedError;
/** Logger instance for error reporting */
logger: Logger;
/** Socket Mode event data */
event: any;
}
/**
* Default error handler for Socket Mode event processing
* @param args - Error handler arguments
* @returns Promise resolving to boolean indicating if event should be retried
*/
function defaultProcessEventErrorHandler(
args: SocketModeReceiverProcessEventErrorHandlerArgs
): Promise<boolean>;Usage Examples:
import { App, HTTPReceiver, SocketModeReceiver } from "@slack/bolt";
// Custom HTTP receiver with error handling
const httpReceiver = new HTTPReceiver({
signingSecret: process.env.SLACK_SIGNING_SECRET!,
// Custom dispatch error handler
dispatchErrorHandler: async ({ error, logger, request, response }) => {
logger.error("Request dispatch failed", {
error: error.message,
url: request.url,
method: request.method
});
response.writeHead(500);
response.end("Internal Server Error");
},
// Custom event processing error handler
processEventErrorHandler: async ({ error, logger, request, response, storedResponse }) => {
logger.error("Event processing failed", {
error: error.message,
hasStoredResponse: !!storedResponse
});
// Return true to indicate the request should not be retried
return true;
},
// Custom unhandled request handler
unhandledRequestHandler: ({ logger, request, response }) => {
logger.warn("Unhandled request", {
url: request.url,
method: request.method,
headers: request.headers
});
response.writeHead(404);
response.end("Not Found");
}
});
// Socket Mode with custom error handling
const socketReceiver = new SocketModeReceiver({
appToken: process.env.SLACK_APP_TOKEN!,
clientOptions: {
// Socket Mode specific options
pingInterval: 30000,
}
});
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
receiver: httpReceiver, // or socketReceiver
});
// Global error handler for the app
app.error(async (error) => {
console.error("Slack Bolt app error:", error);
// Send to monitoring service
if (error.code === "slack_bolt_receiver_authenticity_error") {
console.error("Authentication error - check signing secret");
} else if (error.code === "slack_bolt_authorization_error") {
console.error("Authorization error - check tokens and permissions");
}
});Extract custom properties from requests for enhanced context.
/**
* Custom properties extractor function type
* Used to add custom data to request context from incoming requests
*/
type CustomPropertiesExtractor = (
request: BufferedIncomingMessage
) => StringIndexed;Usage Example:
import { App, ExpressReceiver } from "@slack/bolt";
const receiver = new ExpressReceiver({
signingSecret: process.env.SLACK_SIGNING_SECRET!,
// Extract custom properties from request
customPropertiesExtractor: (request) => {
return {
// Add request timing
requestReceivedAt: Date.now(),
// Add request metadata
userAgent: request.headers["user-agent"] as string,
// Add custom headers
customHeader: request.headers["x-custom-header"] as string,
// Add derived properties
isRetry: !!(request.headers["x-slack-retry-num"]),
retryNum: parseInt(request.headers["x-slack-retry-num"] as string) || 0
};
}
});
const app = new App({
receiver,
token: process.env.SLACK_BOT_TOKEN
});
// Use custom properties in middleware
app.use(async ({ context, logger, next }) => {
logger.info("Request metadata", {
receivedAt: context.requestReceivedAt,
userAgent: context.userAgent,
isRetry: context.isRetry,
retryNum: context.retryNum
});
await next();
});// Store sensitive values in environment variables
const config = {
signingSecret: process.env.SLACK_SIGNING_SECRET!, // Required for verification
botToken: process.env.SLACK_BOT_TOKEN!, // Required for API calls
appToken: process.env.SLACK_APP_TOKEN, // Required for Socket Mode
clientId: process.env.SLACK_CLIENT_ID, // Required for OAuth
clientSecret: process.env.SLACK_CLIENT_SECRET // Required for OAuth
};
// Validate required environment variables
if (!config.signingSecret) {
throw new Error("SLACK_SIGNING_SECRET environment variable is required");
}import { App, isValidSlackRequest } from "@slack/bolt";
// Always validate requests in production
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
signingSecret: process.env.SLACK_SIGNING_SECRET,
// Enable request verification (default: true)
// Only disable in development/testing environments
processBeforeResponse: false, // Set to true for serverless environments
});
// Manual validation example
function validateSlackRequest(body: string, headers: Record<string, string>): boolean {
return isValidSlackRequest({
signingSecret: process.env.SLACK_SIGNING_SECRET!,
body,
headers
});
}Install with Tessl CLI
npx tessl i tessl/npm-slack--bolt