How to Send Brand Mention Alerts to Slack
A complete, dependency-free Node.js script: stream brand mentions over SSE and post each one to a Slack channel
Written by Jacob Partington
July 6, 2026
How to send brand mention alerts to Slack
To get brand mentions in Slack the moment they are published, open one SSE connection to /v1/news/stream filtered to your brand and POST each incoming article to a Slack incoming webhook. The whole pipeline is one Node.js script with no dependencies — about 50 lines, shown in full below — and it reconnects on drops without losing articles.
Step 1: create a Slack incoming webhook
In Slack, create an app (or open an existing one) at api.slack.com/apps, enable Incoming Webhooks, and add a webhook for the channel that should receive alerts. Slack gives you a URL of the form https://hooks.slack.com/services/T000/B000/XXXX — posting JSON like {"text": "hello"} to it puts a message in the channel. Treat the URL as a secret: anyone who has it can post to your channel.
Step 2: choose the stream filters
The stream accepts the same filters as a normal search, so build the query on /v1/news/everything first, confirm it returns the coverage you expect, then reuse it verbatim. For a company, filter with organization.name; for a consumer brand, brand.name; and to alert only on reputation risks, add entity.sentiment.polarity=negative — the tone measured toward your brand specifically, explained in how to detect negative mentions quickly:
curl -N -H "X-API-Key: YOUR_API_KEY" \
"https://api.apitube.io/v1/news/stream?organization.name=Tesla&language.code=en&fl=id,title,href,source.domain"
The fl parameter trims each event to the fields the alert needs, so you are not parsing full article bodies just to build one Slack line.
Step 3: the complete alert script
Save this as brand-alerts.mjs and run it with node brand-alerts.mjs (Node 20+; fetch is built in). It parses the SSE wire format, posts every article event to Slack, and reconnects with Last-Event-Id after any drop so no mention is lost:
const API_KEY = process.env.APITUBE_API_KEY;
const SLACK_WEBHOOK_URL = process.env.SLACK_WEBHOOK_URL;
const FILTERS = "organization.name=Tesla&language.code=en&fl=id,title,href,source.domain";
let lastEventId = null;
async function postToSlack(article) {
await fetch(SLACK_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
text: `New mention: <${article.href}|${article.title}> — ${article.source.domain}`
})
});
}
async function streamOnce() {
const headers = { "X-API-Key": API_KEY };
if (lastEventId) headers["Last-Event-Id"] = lastEventId;
const res = await fetch(`https://api.apitube.io/v1/news/stream?${FILTERS}`, { headers });
if (!res.ok) throw new Error(`stream refused: HTTP ${res.status}`);
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let event = { name: "", id: "", data: "" };
for (;;) {
const { value, done } = await reader.read();
if (done) return;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop();
for (const line of lines) {
if (line.startsWith("event:")) event.name = line.slice(6).trim();
else if (line.startsWith("id:")) event.id = line.slice(3).trim();
else if (line.startsWith("data:")) event.data += line.slice(5).trim();
else if (line === "") {
if (event.name === "article" && event.data) {
lastEventId = event.id;
await postToSlack(JSON.parse(event.data));
} else if (event.name === "error") {
console.error("stream error:", event.data);
}
event = { name: "", id: "", data: "" };
}
}
}
}
for (;;) {
try {
await streamOnce();
} catch (err) {
console.error(err.message);
}
await new Promise(resolve => setTimeout(resolve, 5000));
}
The outer loop is the resilience layer: the server ends every session after 6 hours by design, and networks drop — either way the script waits five seconds and reconnects, replaying Last-Event-Id so APITube first delivers everything you missed. The wire format details (event names, heartbeats, error events) are in how to stream news with SSE.
Step 4: test it without spending credits
Run the script with a test key first: the stream sends real article events with masked content, and deliveries do not consume your quota. You will see masked titles arrive in Slack — proof the parsing, posting and reconnection all work. Then switch APITUBE_API_KEY to a live key for full content.
Common Questions
Why SSE instead of a webhook?
A webhook needs a public HTTPS endpoint that APITube can reach; the SSE script runs anywhere — a laptop, a container, a box behind NAT — because it dials out. If you already run a public server, a webhook removes the long-lived connection from your side: register the same filters as a saved search and let APITube POST to you instead. Both deliver identical article objects.
What does this cost?
One streaming credit per article actually delivered; the idle connection and heartbeats are free, so a quiet brand costs nothing between mentions. Note that streaming is not available on the Free plan — on Free, run the same filters as a polling loop instead, as shown in polling vs streaming.
Can I watch several brands with one script?
Yes — organization.name accepts up to three comma-separated values with OR logic (organization.name=Tesla,Rivian), and the same goes for brand.name. Past three, run one stream per group of three, or one stream per brand if you want per-channel routing in Slack: each stream stays within your plan’s connection slots.