BlogSlack Webhooks

How to Test Slack Webhooks

Slack sends webhook events when users interact with your app — messages, reactions, slash commands, and interactive components.

Looking for the broader picture? See the 7 best webhook testing tools (2026), or if you're already on Webhook.site, the 60-second migration to HookRay.

Slack Official Webhook Docs

1. Slack Webhook Events

Slack can send the following webhook events to your endpoint:

message
app_mention
reaction_added
member_joined_channel
channel_created
team_join
file_shared
link_shared
app_home_opened
workflow_step_execute

2. Set Up a Test Endpoint with HookRay

Follow these steps to start receiving Slack webhooks for testing:

  1. Go to HookRay and click "Start Testing — Free" to get your unique webhook URL.
  2. Copy the URL (e.g., https://h.hookray.com/abc123).
  3. In your Slack dashboard, navigate to the webhook settings and paste the HookRay URL as your endpoint.
  4. Select the events you want to receive (see list above).
  5. Trigger a test event — HookRay will show the incoming webhook in real-time.

3. Sample Slack Webhook Payload

Here's an example of what a Slack webhook payload looks like:

payload.json
{
  "type": "event_callback",
  "event": {
    "type": "message",
    "channel": "C0123456789",
    "user": "U0123456789",
    "text": "Hello from Slack!",
    "ts": "1710000000.000000"
  },
  "team_id": "T0123456789"
}

4. How to Verify Slack Webhook Signatures

Signing details
Algorithm
HMAC-SHA256
Header
X-Slack-Signature
Encoding
hex
Prefix
v0=

Slack signs the string `v0:{X-Slack-Request-Timestamp}:{raw_request_body}`. You must read both the timestamp and signature headers.

Node.js (Express)

import crypto from 'node:crypto';
import express from 'express';

const app = express();

app.post(
  '/webhooks/slack',
  express.raw({ type: 'application/x-www-form-urlencoded' }),
  (req, res) => {
    const timestamp = req.headers['x-slack-request-timestamp'] as string;
    const signature = req.headers['x-slack-signature'] as string;

    // Reject requests older than 5 minutes (replay protection).
    const fiveMinutes = 60 * 5;
    if (Math.abs(Math.floor(Date.now() / 1000) - parseInt(timestamp, 10)) > fiveMinutes) {
      return res.status(401).send('stale request');
    }

    const baseString = `v0:${timestamp}:${req.body.toString('utf8')}`;
    const expected =
      'v0=' +
      crypto
        .createHmac('sha256', process.env.SLACK_SIGNING_SECRET!)
        .update(baseString)
        .digest('hex');

    if (
      signature.length !== expected.length ||
      !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
    ) {
      return res.status(401).send('invalid signature');
    }

    res.json({ ok: true });
  },
);

Python (FastAPI)

import hmac, hashlib, os, time
from fastapi import FastAPI, Request, HTTPException

app = FastAPI()

@app.post("/webhooks/slack")
async def slack_webhook(request: Request):
    body = await request.body()
    timestamp = request.headers.get('x-slack-request-timestamp', '0')
    signature = request.headers.get('x-slack-signature', '')

    if abs(int(time.time()) - int(timestamp)) > 60 * 5:
        raise HTTPException(status_code=401, detail='stale request')

    base_string = f"v0:{timestamp}:{body.decode('utf-8')}".encode()
    expected = 'v0=' + hmac.new(
        os.environ['SLACK_SIGNING_SECRET'].encode(),
        msg=base_string,
        digestmod=hashlib.sha256,
    ).hexdigest()
    if not hmac.compare_digest(signature, expected):
        raise HTTPException(status_code=401, detail='invalid signature')
    return {'ok': True}
Watch out: The 5-minute timestamp window is critical — without it, an attacker who replays a captured request can pass signature verification indefinitely. Slack's docs say to reject anything outside ±5 minutes.

Capture a real Slack webhook with HookRay first, then replay the captured request against your verifier locally — that way you can iterate on the verification code without re-triggering events in Slack. Read Slack's official signing docs for the canonical reference, or see the cross-service signature verification guide for Ruby and timing-safe comparison patterns.

5. How Slack Retries Failed Webhooks

Retry policy
Max attempts
3
Total window
~36 minutes total
Backoff
1 min, 5 min, 30 min
Retries on
Non-2xx responses, timeouts (3s for slash commands)
Stops on
Any 2xx response within 3s
Watch out: Slack is the outlier — only 3 retries over 36 minutes. A longer outage drops Slack events on the floor. If Slack signals are critical (Slash commands, Events API), monitor delivery aggressively and consider a separate ingestion buffer.

If your handler is going to see the same Slack event multiple times, you need idempotency by event ID and the right response codes — see the Webhook Retry Strategies guide for the full pattern (idempotency tables, dead-letter queues, replaying captured events for tests). Cross-reference with Slack's official retry docs before relying on these numbers in production.

6. Frequently Asked Questions

How do I test Slack webhooks without deploying?

Use HookRay to get an instant public webhook URL. Paste it into your Slack dashboard's webhook configuration, trigger an event, and watch the payload arrive in real time. No code, no ngrok, no deployment required. The free tier captures 100 requests per month and works on all Slack event types.

Why aren't my Slack webhooks arriving?

The four most common causes: (1) the endpoint URL isn't publicly accessible — Slack can't reach localhost; (2) the wrong events are subscribed in your Slack dashboard; (3) signature verification is rejecting the request before your handler runs; (4) Slack can't reach your server because of a firewall, expired SSL certificate, or wrong DNS. Use HookRay's URL to isolate which of these four is failing — if HookRay receives the webhook, the problem is in your handler. If HookRay doesn't, the problem is in Slack configuration.

Why am I getting 400 or 500 errors from my Slack webhook?

Slack reports the response status your endpoint returned. HookRay accepts any payload and returns 200 OK by default, so if you see 400/500 in your Slack dashboard while pointing at HookRay, the issue is in Slack's configuration (wrong event, malformed signing secret, etc.). If you point at your own endpoint and get 400/500, the issue is in your handler — capture the request with HookRay, replay it locally, and debug from the captured payload.

How do I verify Slack webhook signatures?

Slack signs each webhook request with a shared secret. Capture the raw headers and body using HookRay, then verify the signature in your application using Slack's SDK or a standard HMAC library. Once verification works against HookRay-captured data, you can safely deploy. Slack's docs (linked above) cover the exact signing algorithm.

Can I replay a captured Slack webhook?

Yes — HookRay's replay feature re-sends any captured webhook to a different endpoint with one click. This is the fastest way to fix a buggy handler: capture the payload once, fix your code, and replay until it works. No need to re-trigger the event in Slack.

7. Next Steps

  • Use HookRay's webhook replay feature to re-send captured webhooks while building your handler
  • Enable smart parsing (Pro plan) to see Slack-specific fields highlighted automatically
  • Check the Slack webhook documentation for the complete event reference

Ready to test Slack webhooks?

Get a free webhook URL in 5 seconds. No signup required.

Start Testing Slack Webhooks — Free

Free PDF: Webhook Testing Cheat Sheet 2026

One-page reference for 50+ APIs — canonical events, signing methods, sample payloads. Print it, pin it, share it.

📄 Download the cheat sheet (PDF, 180KB)