DEV Community

Henry Hang
Henry Hang

Posted on • Originally published at hookcap.dev

How to Test Slack Webhooks with HookCap

Slack's platform sends webhooks for everything: messages posted in channels, button clicks in interactive messages, slash command invocations, workflow step callbacks, and app lifecycle events. If you're building a Slack app, you need a reliable way to capture and inspect these events during development.

The challenge is familiar: Slack requires a publicly reachable HTTPS URL, but your app is running on localhost:3000. You need to see the exact payload structure Slack sends, replay events to test edge cases, and iterate quickly without redeploying.

This guide covers using HookCap as your Slack webhook testing tool.

Originally published at hookcap.dev

Types of Slack Webhooks

Slack sends events through several different mechanisms, each with its own payload format:

Type Where to configure What it sends
Event Subscriptions App Settings > Event Subscriptions Channel messages, reactions, member joins, file uploads
Interactivity App Settings > Interactivity & Shortcuts Button clicks, modal submissions, menu selections
Slash Commands App Settings > Slash Commands User-typed commands like /deploy or /ticket
Incoming Webhooks App Settings > Incoming Webhooks Outbound only (you send TO Slack) — not relevant here

Event Subscriptions and Interactivity are the two you will work with most. They have different payload structures, different verification requirements, and different response expectations.

Setting Up HookCap for Slack Webhooks

1. Create a HookCap endpoint

Sign in at hookcap.dev and create a new endpoint. You get a permanent HTTPS URL like:

https://hook.hookcap.dev/ep_a1b2c3d4e5f6
Enter fullscreen mode Exit fullscreen mode

This URL stays active across sessions. Register it once in your Slack app and it keeps receiving events.

2. Handle the URL verification challenge

When you first enter your HookCap endpoint URL in Slack's Event Subscriptions settings, Slack sends a verification challenge — a POST request with this body:

{
  "token": "Jhj5dZrVaK7ZwHHjRyZWjbDl",
  "challenge": "3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P",
  "type": "url_verification"
}
Enter fullscreen mode Exit fullscreen mode

Slack expects your endpoint to respond with the challenge value in the response body. HookCap captures this delivery for inspection, but since it responds with a 200 OK and not the challenge string, the verification will not pass directly.

To pass URL verification:

Use HookCap's auto-forward feature (Pro plan) to forward events to your local dev server, which handles the challenge response. Your local handler returns the challenge value, and Slack completes verification. After verification succeeds, all subsequent events continue to flow through HookCap for inspection.

Alternatively, temporarily point Slack to your local handler via a tunnel for the initial verification, then switch the URL to your HookCap endpoint for ongoing development.

3. Configure Event Subscriptions

In your Slack app settings under Event Subscriptions:

  1. Toggle Enable Events on
  2. Enter your HookCap endpoint URL
  3. Subscribe to the bot events you need

Common bot events to subscribe to:

Event When it fires
message.channels A message is posted to a public channel your bot is in
message.groups A message is posted to a private channel your bot is in
message.im A direct message is sent to your bot
app_mention Your bot is @mentioned in a channel
reaction_added A reaction emoji is added to a message
member_joined_channel A user joins a channel
channel_created A new channel is created

4. Configure Interactivity (if needed)

If your app uses buttons, menus, modals, or shortcuts, go to Interactivity & Shortcuts and enter your HookCap endpoint URL as the Request URL.

Inspecting Slack Event Payloads

Event Subscriptions payload

Every event delivery from Slack follows this envelope structure:

{
  "token": "ZZZZZZWSxiZZZ2yIvs3peJ",
  "team_id": "T061EG9R6",
  "api_app_id": "A0MDYCDME",
  "event": {
    "type": "message",
    "channel": "C2147483705",
    "user": "U2147483697",
    "text": "Hello world",
    "ts": "1355517523.000005",
    "event_ts": "1355517523.000005",
    "channel_type": "channel"
  },
  "type": "event_callback",
  "event_id": "Ev0PV52K25",
  "event_time": 1355517523
}
Enter fullscreen mode Exit fullscreen mode

Interactivity payload

Interactivity payloads arrive as form-encoded POST with a single payload field containing JSON:

{
  "type": "block_actions",
  "user": { "id": "U2147483697", "name": "jsmith" },
  "trigger_id": "13345224609.8534564800.6f8ab1f0f3c9060c0c24a0ef07",
  "actions": [{
    "type": "button",
    "action_id": "approve_request",
    "value": "request_123"
  }]
}
Enter fullscreen mode Exit fullscreen mode

Slash command payload

token=gIkuvaNzQIHg97ATvDxqgjtO
&team_id=T0001
&channel_id=C2147483705
&user_id=U2147483697
&command=%2Fdeploy
&text=production+v1.2.3
&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2F...
Enter fullscreen mode Exit fullscreen mode

Auto-Forward to Your Local Dev Server

With HookCap's auto-forward feature (Pro plan), Slack events are simultaneously captured and forwarded to your local development server.

Set up auto-forward:

Forward URL: http://localhost:3000/slack/events
Enter fullscreen mode Exit fullscreen mode

Now when Slack sends an event:

  1. HookCap captures and stores the full request
  2. HookCap forwards the request to your local server
  3. Your local server processes the event and returns a response

This eliminates the need for a separate tunnel tool.

Slack Request Verification

Slack signs every outgoing request using your app's Signing Secret:

const crypto = require("crypto");

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

  const fiveMinutesAgo = Math.floor(Date.now() / 1000) - 60 * 5;
  if (parseInt(timestamp) < fiveMinutesAgo) return false;

  const sigBasestring = `v0:${timestamp}:${req.rawBody}`;
  const computed = "v0=" + crypto
    .createHmac("sha256", signingSecret)
    .update(sigBasestring, "utf8")
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(computed),
    Buffer.from(signature)
  );
}
Enter fullscreen mode Exit fullscreen mode

Important: During replay, the timestamp check will fail because the original timestamp is old. For development, skip it conditionally:

if (process.env.NODE_ENV !== "development") {
  const fiveMinutesAgo = Math.floor(Date.now() / 1000) - 60 * 5;
  if (parseInt(timestamp) < fiveMinutesAgo) return false;
}
Enter fullscreen mode Exit fullscreen mode

Common Slack Webhook Gotchas

3-second timeout: Slack expects a response within 3 seconds. For long operations, respond with 200 OK immediately and process async.

Duplicate delivery: Use event_id for deduplication — Slack may send the same event multiple times.

Bot message loop: Filter out bot messages to avoid infinite loops when your bot posts in subscribed channels:

if (event.bot_id || event.subtype === "bot_message") {
  return res.status(200).send();
}
Enter fullscreen mode Exit fullscreen mode

response_url expiry: Interactivity and slash command response_url values expire after 30 minutes — replaying old events won't be able to POST back to Slack.

Full Development Workflow

  1. Create a HookCap endpoint and configure it in your Slack app settings
  2. Handle URL verification using auto-forward or a temporary tunnel
  3. Trigger events in your Slack workspace
  4. Inspect payloads in HookCap
  5. Write handlers against actual payload shapes
  6. Replay events as you iterate
  7. Test edge cases with specific replayed payloads
  8. Verify signature handling before deploying to production

Free tier available at hookcap.dev — includes capture, inspect, replay, and real-time streaming. Pro plans start at $12/month for auto-forward to localhost.

Top comments (0)