Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
YouTube Security VideosVisual Studio Code: VS Code Learn: Extending Agents(24.09.2026 um 21:00 Uhr)
•
YouTube Security VideosGoogle Cloud Tech: Turn Audio into Action with Gemini 3.5 Transcribe(24.09.2026 um 21:00 Uhr)
••••
Unix & Linux ServerUSN-8815-1: libass vulnerabilities(24.09.2026 um 16:57 Uhr)
•••••
YouTube Security VideosVisual Studio Code: VS Code Learn: Extending Agents(24.09.2026 um 21:00 Uhr)
•
YouTube Security VideosGoogle Cloud Tech: Turn Audio into Action with Gemini 3.5 Transcribe(24.09.2026 um 21:00 Uhr)
••••
Unix & Linux ServerUSN-8815-1: libass vulnerabilities(24.09.2026 um 16:57 Uhr)
•••••
Intelligence View
⚡ tsecurity.de Intelligence

The Slack Events API: Subscriptions and Event Handlers

The Slack Events API is the part of the platform that lets your app react to what people do in a workspace without polling for it. Where a slash command fires because a user typed something and a shortcut fires because a user clicked…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

The Slack Events API is the part of the platform that lets your app react to what people do in a workspace without polling for it. Where a slash command fires because a user typed something and a shortcut fires because a user clicked something, the Events API works the other way around: Slack pushes an HTTP POST to a URL you own every time an activity you subscribed to happens. Someone mentions your bot, a message lands in a channel it watches, a new teammate joins, and Slack delivers each of those as an event. We built a DevOps bot for a large engineering org on exactly this model, and most of the work was not the handlers themselves but the plumbing around acknowledgment, retries, and deduplication. That plumbing is where teams get burned, so this post walks through it end to end.






Turning on Event Subscriptions



You enable this in your app config at api.slack.com/apps. Pick the app, open Event Subscriptions, flip the toggle, and set a Request URL that Slack will deliver to. Ours was https://devops-bot.example.tech/slack/events.



The moment you paste that URL, Slack tries to validate it. It sends a single POST with a url_verification body before it will let you save anything:




{
"token": "Jhj5dZrVaK7ZwHHjRyZWjbDl",
"challenge": "3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P",
"type": "url_verification"
}






Your endpoint has to reply within 3 seconds, echoing the challenge back:




{ "challenge": "3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P" }






A text/plain body containing the same string also works. Only after Slack sees its challenge come back does it accept the URL. This handshake happens once at setup, not on every event, so treat it as a special case at the top of your handler.






The endpoint: verification, signing, and dispatch



Here is the core of the route. Two things matter: parsing the raw body so you can verify Slack's signature, and answering the verification challenge before you get clever with security checks.




const express = require('express');
const crypto = require('crypto');

const app = express();
app.use('/slack/events', express.json({
verify: (req, _res, buf) => { req.rawBody = buf.toString('utf8'); },
}));

function verifySlack(req) {
const ts = req.headers['x-slack-request-timestamp'];
const sig = req.headers['x-slack-signature'];
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const base = `v0:${ts}:${req.rawBody}`;
const mac = 'v0=' + crypto.createHmac('sha256', process.env.SLACK_SIGNING_SECRET)
.update(base).digest('hex');
return crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(sig));
}

app.post('/slack/events', async (req, res) => {
// Answer the verification handshake first.
if (req.body.type === 'url_verification') {
return res.json({ challenge: req.body.challenge });
}

if (!verifySlack(req)) return res.status(401).send('bad sig');

if (req.body.type === 'event_callback') {
res.status(200).send(''); // ack immediately
enqueueEvent(req.body); // hand off to async worker
return;
}

res.status(400).send('unknown type');
});






The signature verification is standard Slack HMAC over v0:timestamp:body with your signing secret, plus a 5-minute timestamp window to block replays. Use timingSafeEqual, not ===. One subtlety: the url_verification POST also carries a valid X-Slack-Signature, so you can verify it if you want, but handling it before the signature check keeps the handshake bulletproof while you are still wiring things up.



A pitfall we hit early: if your JSON middleware is not scoped to the events route, req.body is undefined and verification "fails" for reasons that have nothing to do with signing. Make sure the body actually parses before you blame your secret.






Subscribing to bot events



Scroll down to Subscribe to bot events and add the ones you care about. For the DevOps bot we used four:




  • app_mention fires when someone tags the bot, like @DevOpsBot deploy stg.

  • message.channels fires on messages in public channels the bot is a member of.

  • team_join fires when someone new joins the workspace.

  • member_joined_channel fires when someone joins a channel the bot is in.



Each event pulls in a required scope: app_mentions:read, channels:history, users:read, and so on. After you add them, hit Save Changes and reinstall the app, because new scopes do not take effect until the workspace reauthorizes. Forgetting the reinstall is the single most common reason "my subscription is set but no events arrive."



There are two families here. Bot events ride your xoxb- bot token and require the bot to actually be in the channel to see activity there. User events like message.im ride an xoxp- user token tied to whoever installed the app. We kept the DevOps bot on bot events only, because a user token dies the day that person leaves, and you do not want your deploy bot going dark because someone changed teams. The tradeoff is reach: with hundreds of channels in this workspace, the bot has to be invited into each one. We scripted that with conversations.list and a bulk conversations.invite rather than typing /invite a hundred times.






Why 3 seconds matters



Slack waits 3 seconds for your 200. Miss it and Slack retries up to 3 times, which means the same event can be delivered four times total. If your handler does real work, talks to a deploy API, writes to a database, posts a message, before it returns the 200, you will blow past the budget under load, Slack will retry while your first handler is still running, and you get duplicate actions. We saw this exact failure: a deploy triggered twice because the handler was slow.



The fix is the shape already in the code above. Acknowledge with res.status(200).send('') the instant the signature checks out, then push the work onto a queue:




function enqueueEvent(envelope) {
const ev = envelope.event;
jobQueue.add({
eventId: envelope.event_id,
teamId: envelope.team_id,
type: ev.type,
payload: ev,
});
}

queueWorker.process(async (job) => {
const { type, payload, teamId, eventId } = job.data;
if (await alreadyProcessed(eventId)) return;
await markProcessed(eventId);

switch (type) {
case 'app_mention': return onAppMention(payload, teamId);
case 'message': return onMessage(payload, teamId);
case 'team_join': return onTeamJoin(payload, teamId);
case 'member_joined_channel': return onMemberJoinedChannel(payload, teamId);
}
});






Delivery is at-least-once, so dedup is not optional. The retry reuses the same event_id, which gives you a natural idempotency key. Redis with SET NX is plenty:




async function alreadyProcessed(eventId) {
const set = await redis.set(`slack:event:${eventId}`, '1', 'NX', 'EX', 86400);
return set === null; // null means the key already existed
}






A 24-hour TTL is comfortable since retries only span a few minutes. The headers X-Slack-Retry-Num and X-Slack-Retry-Reason are also there if you want to detect and short-circuit retries explicitly.






Writing the handlers



With dispatch sorted, the handlers are the easy part. For app_mention, strip the leading mention token out of the text and parse what is left:




async function onAppMention(ev, teamId) {
const cleanText = ev.text.replace(/<@[A-Z0-9]+>/g, '').trim();
const [cmd, env, service] = cleanText.split(/\s+/);

if (cmd === 'deploy') {
if (!['stg', 'prod'].includes(env)) {
return reply(ev, 'I can only deploy stg or prod.');
}
if (env === 'prod') {
return reply(ev, 'Prod deploys need approval. I raised a request to the leads channel.');
}
await triggerDeploy({ env, service, requester: ev.user });
return reply(ev, `Deploying ${service} to ${env}, will report back when done.`);
}
return reply(ev, "I didn't catch that. Mention me with 'help' to see options.");
}

async function reply(ev, text) {
await slack.chat.postMessage({
channel: ev.channel,
thread_ts: ev.thread_ts || ev.ts, // keep it in-thread, not in the main channel
text,
});
}






The message.channels handler is where anti-loop discipline earns its keep. When your bot posts a message, that post is itself a message event, which can trigger your bot to reply, which posts another message, and now you are rate-limited into oblivion. Guard the top of the handler:




async function onMessage(ev, teamId) {
if (ev.bot_id || ev.subtype === 'bot_message') return; // skip all bots, including self
if (ev.subtype) return; // skip edits, deletes, joins

const text = ev.text || '';
if (!SPAM_PATTERNS.some((re) => re.test(text))) return;

try {
await slack.chat.delete({ channel: ev.channel, ts: ev.ts });
} catch (e) {
log.warn('cannot delete', { channel: ev.channel, error: e.data?.error });
}
const im = await slack.conversations.open({ users: ev.user });
await slack.chat.postMessage({ channel: im.channel.id, text: 'That post broke channel rules.' });
}






Checking ev.bot_id is safer than checking against your own bot user ID. If you run dev, staging, and prod copies of the bot in one workspace, each has a different bot user, and ev.bot_id skips all of them in one line.



The team_join and member_joined_channel handlers follow the same pattern: open a DM with conversations.open, then post a welcome with Block Kit or a plain text fallback, auto-inviting new hires into default channels and pointing people who join an alerts channel at the on-call guide before they mute it.






The ordering trap



One more thing the docs underplay: Slack does not guarantee event ordering. Once retries and your own queue get involved, events can arrive out of sequence. We learned this when the bot miscounted reactions because it processed a reaction_removed before the matching reaction_added. If your logic depends on order, do not incrementally fold the event stream into state. Either sort by event_ts or, better, treat the event as a signal to refetch the source of truth, in that case calling reactions.get to read the current state rather than trusting your running tally. The event tells you something changed; the API tells you what it is now.






Takeaway



The Slack Events API is straightforward to turn on and easy to get subtly wrong. Answer the URL verification challenge before anything else, verify signatures on real events, acknowledge inside 3 seconds and do the work in a queue, dedup on event_id, and guard every message handler against bot loops. Get those five right and the handlers themselves are a few lines each. Get them wrong and you ship a bot that double-deploys, talks to itself, and miscounts state, all of which we have watched happen before fixing them.



If you are building Slack automation, internal bots, or event-driven integrations and want them to be production-safe from day one, we do this work. Tell us what you are building at /contact and see how we can help at /service.

SOC Incident Playbook: Remote Code Execution (RCE) Defense
Syntax validiert (0 Fehler)
title: Detect Exploitation - The Slack Events API: Subscriptions and Event Handlers
id: 2ffd4744-9474-4d3f-8764-c742d663830e
status: experimental
description: Automatisch generierte SIEM-Erkennungsregel basierend auf CTI Intelligence
references:
  - https://tsecurity.de/
author: iShareStuff CTI Automated Detection Engine
date: 2026-09-24
logsource:
  category: network_connection
  product: any
detection:
  selection:
      CommandLine|contains:
        - 'exploit'
  condition: selection
falsepositives:
  - Legitime administrative Zugriffe oder Penetrationstests
level: high
tags:
  - attack.initial_access
Syntax validiert (0 Fehler)
rule CTI_Threat_Indicator {
    meta:
        author = "iShareStuff CTI Automated Detection Engine"
        date = "2026-09-24"
        description = "YARA Signature for "
    strings:
        $str = "The Slack Events API: Subscrip" ascii wide
    condition:
        any of them
}
Syntax validiert (0 Fehler)
index=security sourcetype IN ("cisco:asa", "pan:traffic", "zeek_conn", "suricata", "WinEventLog:Security")
("The Slack Events API Subscriptions and E")
| stats count earliest(_time) as first_seen latest(_time) as last_seen by src_ip, dest_ip, dest_host, signature
| eval first_seen=strftime(first_seen, "%Y-%m-%d %H:%M:%S"), last_seen=strftime(last_seen, "%Y-%m-%d %H:%M:%S")
| sort - count
Syntax validiert (0 Fehler)
message: "*The Slack Events API Subscriptions and E*"
Syntax validiert (0 Fehler)
CommonSecurityLog
| where Message has "The Slack Events API Subscriptions and E"
| summarize EventCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by SourceIP, DestinationIP, DestinationPort, Activity
| extend DetectionRule = "iShareStuff-CTI-Compiled"
| sort by EventCount desc
🎯
MITRE ATT&CK Matrix Navigator 14 Taktiken
Reconnaissance
-
Resource Development
-
Initial Access
Execution
Persistence
-
Privilege Escalation
Defense Evasion
Credential Access
-
Discovery
-
Lateral Movement
-
Collection
-
Command and Control
Exfiltration
-
Impact
tsecurity.de Cognitive Threat RAG
Fokus-Vektor:

Kognitive Analyse für identifizierte Bedrohung: Erhöhte Bedrohungslage im Bereich The Slack Events API: Subscriptions and .... Basierend auf 368k Vektor-Korrelationen werden sofortige Isolationsmaßnahmen für betroffene Endpunkte empfohlen.

🛡️ Angriffsfläche & Exposure

Netzwerk/Remote-Zugriff ohne Vorauthentifizierung möglich.

⚡ Empfohlene Sofortmaßnahmen
  • 1. Perimeter-Inspektion: Relevante Portfreigaben und exponierte Endpunkte unverzüglich scannen.
  • 2. Patch-Applikation: Hersteller-Hotfix einspielen oder betroffene Daemons in isolierte DMZ-Segmente überführen.
  • 3. Telemetrie & EDR-Alerts: Prozessaufrufe und Child-Processes auf anomale Shell-Spawns überwachen.
🔗 Semantisch verwandte Zero-Days MariaDB 11.7 VEC
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten The Slack Events API: Subscriptions and Event Handlers

Thematisch verwandte Begriffe: Slack, Events, Subscriptions, Event · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-61823 | code16 Sharp is a Laravel-based framework for building content-managemen…
Advisory →
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel • Rechts: nächster Artikel • unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel TTP ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...
↗ Original-Quelle