Field notes
How Slack behaves, and where it bites
A handshake before you start, a three second budget after, and a signature that binds a timestamp.
How Slack sends
Slack POSTs JSON with the type in event.type (message, app_mention), signs with X-Slack-Signature over a versioned string, and sends the timestamp separately in X-Slack-Request-Timestamp.
Where it bites
- Registration is a challenge, not a form. Slack posts url_verification and expects your endpoint to echo the token back before it will save the URL at all.
- Three seconds, then a retry. Slow handlers get retried, and repeated failure gets the subscription disabled.
- Retries look like duplicates. Slack marks them, but only if you kept the headers to tell them apart.
The vault under it
Point a WebhookVault endpoint at Slack and every delivery is stored in full: headers verbatim, bodies byte-for-byte, labelled with its event type and searchable. Forward it onward to your own handler if you want the relay pattern, replay any delivery once the bug is fixed, and let a Watchdog notice the day it stops arriving.
Create an endpoint
Name it slack-events. Turn on forwarding to your own handler if you want the vault in front of it rather than beside it.
Register the capture URL
api.slack.com → your app → Event Subscriptions. Paste the capture URL; the vault answers the url_verification challenge for you, so it verifies on the spot.
Keep everything
Every delivery from here on is on the shelf: searchable, replayable, and watched for the day it goes quiet.
Verify from the record, not just at the door
Slack signs v0:timestamp:body with your signing secret. The vault checks the pair and enforces the tolerance, so a replayed delivery does not pass as fresh.
The vault stores the signature headers verbatim with every request, so a delivery from last month can still be checked today. Verdicts are part of Solo and up. How verification works →
# headers, stored verbatim "X-Slack-Signature": "v0=8f3a2c41d9e6…" "X-Slack-Request-Timestamp": "1757030400" { "type": "event_callback", "team_id": "T024BE7LD", "event": { "type": "app_mention", "user": "U061F7AUR", "text": "<@U0LAN0Z> deploy staging" } }
Event catalogue
Slack webhook events you'll see
A few of the events Slack sends, not the full list: Slack's own documentation has every one. Each arrives in the vault labelled with its event type.
| Event | What it tells you |
|---|---|
| url_verification | Sent once when you save the Request URL; the body carries a challenge to echo back. |
| app_mention | Someone mentioned your app in a channel. |
| message.channels | A message was posted in a public channel your app is in (its body type is just message; message.im covers direct messages). |
| reaction_added | A reaction was added to a message or file. |
| member_joined_channel | A user joined a channel. |
| app_uninstalled | A workspace removed your app. |
Questions
Slack webhook questions, answered
The three that come up most when Slack is wired to a new endpoint.
Why does Slack keep sending the same event again?
Slack expects a 2xx within three seconds. If it does not get one it retries, up to three more times, and marks each retry with X-Slack-Retry-Num and X-Slack-Retry-Reason. A WebhookVault capture URL answers as soon as the event is stored, so a slow handler of yours does not trigger retries, and the vault keeps those headers so any retry that does arrive can be told from the original.
How do I verify the X-Slack-Signature header?
Build the string v0:{timestamp}:{raw body} from the X-Slack-Request-Timestamp header and the raw body, compute HMAC-SHA256 with your app's signing secret, prefix the hex digest with v0= and compare it to the header in constant time. Refuse a timestamp more than a few minutes old. On Solo and up the vault does this at capture and enforces the tolerance.
Why won't Slack accept my Request URL?
When you save the URL Slack POSTs a url_verification payload and expects the challenge value back in a 200 response. A capture URL whose provider is set to Slack answers that handshake for you, so the URL saves without a temporary responder.
Let the vault answer the handshake.
Free tier, no card. Registration succeeds without you writing a temporary responder.
Sign up with GitHub, Google or email. Two-factor authentication is required on every account, Free included, for account security.