Slack Link Unfurling for Internal Ops Tools: Turn Bare URLs Into Context
Build a Slack link-unfurling bot that turns internal dashboard and runbook URLs into rich Block Kit previews, with AI scaffolding you review before shipping.
- #slack
- #chatops
- #block-kit
- #events-api
Half the links pasted into my ops channels are dead weight. Someone drops a Grafana URL during an incident and everyone else has to click through, load a dashboard, and figure out which panel mattered. A pasted runbook link is just blue text until you open it. Public sites get nice rich previews in Slack automatically, but your internal tools — behind auth, on private hostnames — show up as bare, contextless URLs. Link unfurling fixes that: you own a domain pattern, and when someone pastes a matching link, your bot decorates it with a preview right there in the thread.
I built mine with AI help, and I treat that help the way I treat any fast junior engineer: great at boilerplate, careless about auth. The model will happily write an unfurl handler that fetches your internal dashboard with no credentials check and echoes whatever it gets back into a public-ish channel. You review what it renders before it goes near a workspace, because an unfurl is a place internal data leaks in plain sight.
How unfurling actually works
You register URL domains your app owns in the app config (or manifest), and Slack sends a link_shared event whenever someone posts a matching link. You respond by calling chat.unfurl with the message timestamp and a map of URL to Block Kit attachment.
# manifest snippet
settings:
event_subscriptions:
bot_events:
- link_shared
unfurl_domains:
- grafana.internal.example.com
- runbooks.example.com
The event arrives looking like this:
{
"type": "link_shared",
"channel": "C123",
"message_ts": "1718600000.000100",
"links": [
{ "domain": "grafana.internal.example.com", "url": "https://grafana.internal.example.com/d/abc?panel=4" }
]
}
Build the unfurl handler
Match the link, derive context, and respond. The key API is chat.unfurl, which keys previews by the exact URL string:
app.event('link_shared', async ({ event, client }) => {
const unfurls = {};
for (const { url } of event.links) {
const ctx = await describeLink(url); // your logic
if (!ctx) continue; // unknown → leave it bare
unfurls[url] = {
blocks: [
{ type: 'section', text: { type: 'mrkdwn', text: `*${ctx.title}*\n${ctx.summary}` } },
{ type: 'context', elements: [{ type: 'mrkdwn', text: ctx.meta }] },
],
};
}
if (Object.keys(unfurls).length === 0) return;
await client.chat.unfurl({ channel: event.channel, ts: event.message_ts, unfurls });
});
Note the continue when you can’t describe a link — silently leaving it bare is the correct, safe default.
Parse the URL; don’t fetch blindly
The tempting design is to fetch the URL server-side and scrape a title. For internal tools that’s an auth nightmare and a leak risk. Better: parse the URL structure and look up metadata from a source you already trust.
function describeGrafanaUrl(url) {
const u = new URL(url);
const dashId = u.pathname.split('/')[2];
const panel = u.searchParams.get('panel');
const meta = dashboardCatalog[dashId]; // internal catalog, not a live scrape
if (!meta) return null;
return {
title: meta.name,
summary: panel ? `Panel: ${meta.panels[panel] ?? panel}` : meta.description,
meta: `Owned by ${meta.team} · ${meta.env}`,
};
}
Pro Tip: Unfurled content respects the channel’s visibility, so never put anything in an unfurl you wouldn’t post as a normal message — including in externally shared Slack Connect channels, where an unfurl is visible to the partner org. Gate sensitive unfurls on channel sharedness.
Make incident links carry live status
The highest-value unfurl for ops is the incident link. When someone pastes an incident URL, the preview should show current status, severity, and the on-call owner — pulled at unfurl time, not baked in.
function describeIncidentUrl(url) {
const id = new URL(url).pathname.split('/').pop();
const inc = incidentStore.get(id);
if (!inc) return null;
const emoji = inc.status === 'resolved' ? '✅' : '🔴';
return {
title: `${emoji} ${inc.title}`,
summary: `Severity ${inc.sev} · on-call: <@${inc.oncall}>`,
meta: `Updated ${timeAgo(inc.updatedAt)}`,
};
}
This pairs naturally with your incident response dashboard: the dashboard is the system of record, and the unfurl brings a slice of it inline where the conversation is happening.
Cache, and respect rate limits
link_shared can fire in bursts when someone pastes ten links, and your metadata lookups have a cost. Cache descriptions by URL with a short TTL, and remember chat.unfurl counts against your API limits. If you’re already managing backoff in your app, route unfurl calls through the same queue you use for everything else.
Where AI fits and where it doesn’t
The model is excellent at writing URL parsers, generating the Block Kit, and turning your catalog schema into typed lookups — tedious, mechanical work it does faster than I can. I scaffold those with Cursor or GitHub Copilot and refine the prompts in the prompt workspace. What AI does not get to decide is what’s safe to render. It cannot tell that a dashboard preview exposes customer counts, or that a particular runbook is confidential. You review every unfurl template, and you never give the model real tokens to fetch live internal data. Verify the Slack request signature on the events endpoint, too — unfurl handlers are still untrusted-input handlers.
Conclusion
Link unfurling quietly upgrades every internal URL your team pastes into something with context. Register your domains, respond to link_shared with Block Kit, parse URLs instead of scraping behind auth, and make incident links show live status. Let AI write the parsers and previews fast; keep a human deciding what those previews are allowed to reveal. Explore more in the Slack category or grab a starter from the prompt packs.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.