Slack Webhook Guide

Incoming Webhooks (send to Slack) and Events API (receive from Slack) — setup, verification, and code examples.

Last updated March 2026. Covers Slack API v2 and Bolt SDK.

Two Types of Slack Webhooks

Slack has two distinct webhook systems that work in opposite directions. Understanding which one you need is the first step.

Send to Slack

Incoming Webhooks

You POST a JSON message to a Slack-provided URL, and it appears in a channel. Used for notifications, alerts, deploy messages, and CI/CD updates.

// Send a message to Slack via Incoming Webhook
const webhookUrl = 'https://hooks.slack.com/services/T.../B.../xxx';

await fetch(webhookUrl, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    text: 'Deployment succeeded!',
    blocks: [
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: '*Deployment succeeded* :white_check_mark:\nService api-v2 deployed to production.'
        }
      }
    ]
  })
});
Receive from Slack

Events API

Slack sends HTTP POST requests to YOUR server when events happen — messages posted, reactions added, users joining channels. Used for bots, workflow automation, and integrations.

Common Events API event types:

message app_mention reaction_added reaction_removed channel_created channel_deleted member_joined_channel member_left_channel team_join file_shared app_home_opened link_shared
1

Verifying Slack Signatures

Every Events API request from Slack is signed. You must verify the signature to ensure the request is genuinely from Slack, not an attacker.

Slack uses X-Slack-Signature (HMAC-SHA256) and X-Slack-Request-Timestamp headers:

const crypto = require('crypto');

function verifySlackSignature(req, signingSecret) {
  const timestamp = req.headers['x-slack-request-timestamp'];
  const slackSig = req.headers['x-slack-signature'];

  // Reject requests older than 5 minutes (prevents replay attacks)
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp, 10)) > 300) {
    throw new Error('Request timestamp too old');
  }

  // Compute expected signature
  const sigBaseString = 'v0:' + timestamp + ':' + req.rawBody;
  const mySignature = 'v0=' + crypto
    .createHmac('sha256', signingSecret)
    .update(sigBaseString)
    .digest('hex');

  // Constant-time comparison to prevent timing attacks
  if (!crypto.timingSafeEqual(
    Buffer.from(mySignature),
    Buffer.from(slackSig)
  )) {
    throw new Error('Invalid signature');
  }

  return true;
}

// Express middleware
app.post('/slack/events', express.raw({ type: '*/*' }), (req, res) => {
  req.rawBody = req.body.toString();
  try {
    verifySlackSignature(req, process.env.SLACK_SIGNING_SECRET);
  } catch (err) {
    return res.status(401).json({ error: err.message });
  }

  const event = JSON.parse(req.rawBody);

  // Handle URL verification challenge
  if (event.type === 'url_verification') {
    return res.json({ challenge: event.challenge });
  }

  // Process the event
  console.log('Event:', event.event.type, event.event);
  res.status(200).send();
});

Key details: use the raw request body for signature computation (not parsed JSON). Check the timestamp is within 5 minutes to prevent replay attacks. Use constant-time comparison (timingSafeEqual) to prevent timing side-channel attacks.

2

Setting Up in Slack App Settings

To receive Events API webhooks, configure your Slack App:

  1. Go to api.slack.com/apps and create a new app (or select existing).
  2. Navigate to Event Subscriptions and toggle it on.
  3. Enter your Request URL — this is where Slack sends events. Slack will immediately send a URL verification challenge.
  4. Under Subscribe to bot events, add the events you want (e.g. message.channels, app_mention).
  5. Go to OAuth & Permissions and add the required scopes for your events.
  6. Install the app to your workspace.

For Incoming Webhooks, go to Incoming Webhooks in your app settings, activate it, and click "Add New Webhook to Workspace" to select a channel.

3

Testing with SDNBros

During development you need a public URL for Slack to reach. The SDNBros Webhook Tester gives you one instantly:

  1. Open the Webhook Tester — copy your public URL.
  2. Paste it as your Events API Request URL in Slack App settings.
  3. SDNBros automatically responds to URL verification challenges.
  4. Trigger events in Slack (post a message, add a reaction) and see them arrive in real time.

Common Errors

ErrorCauseFix
url_verification failedYour endpoint did not respond with the challenge value, or responded too slowly (>3s)Return { "challenge": event.challenge } immediately. Do not process anything else during verification.
Invalid signatureUsing parsed JSON body instead of raw body, or wrong signing secretUse raw request body for HMAC. Find your Signing Secret in App Settings > Basic Information (not the bot token).
Request timestamp too oldYour server clock is more than 5 minutes out of sync with SlackSync your server clock with NTP. Check if a proxy is caching/delaying requests.
dispatch_failedSlack could not reach your endpoint or it returned non-2xx within 3 secondsEnsure your URL is publicly accessible over HTTPS. Return 200 quickly and process events asynchronously.
Duplicate eventsSlack retries if your endpoint does not respond within 3 secondsReturn 200 immediately. Use the event_id field to deduplicate. Process events in a background queue.

Test Slack webhooks free

Create a public endpoint and inspect Slack events and URL-verification challenges.

Open Webhook Tester Monitor in Production

Related guides: What is a Webhook? · Webhook vs API · Webhook Security · Retry Strategies · Stripe · GitHub · Shopify

More: What is a Webhook? | Webhook Security Guide | Stripe Webhooks | Retry Strategies