CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-slack--bolt

A framework for building Slack apps, fast.

Pending
Overview
Eval results
Files

request-verification.mddocs/

Request Verification

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.

Capabilities

Request Verification Functions

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);
}

HTTP Module Error Handling

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;
}

Buffered Request Handling

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>;
}

Socket Mode Error Handling

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");
  }
});

Custom Property Extraction

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();
});

Security Best Practices

Environment Configuration

// 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");
}

Request Validation

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

docs

advanced-features.md

core-application.md

index.md

middleware.md

receivers.md

request-verification.md

tile.json