Broken Link Alerts in Slack: How to Set Up a Checker That Posts to Your Channel
· 7 min read
Email alerts about broken links tend to get lost. They go to one person, they land between invoices and newsletters, and when that person is on holiday nobody sees them. Posting alerts to a Slack channel fixes most of that. The whole team sees the alert, someone claims it with an emoji, and the thread becomes a record of the fix.
This guide covers what a useful broken-link alert looks like, the three ways to get alerts into Slack (with the limits of each), and how to stop the channel turning into noise that everyone mutes.
What a good broken-link alert contains
Before wiring anything up, decide what each message has to say. An alert that reads only "3 broken links found" makes someone log in to a dashboard, and most of the time they won't. A useful alert answers four questions in the message itself:
- Which site? This matters if you look after more than one.
- Which URL is broken, and how? A 404 (page gone) needs a redirect or a link edit. A 5xx (server error) might fix itself or might mean a dead plugin. "Unreachable" often means a DNS problem or an expired domain on an external site.
- Where is the link? You need the page the broken link sits on, because that page is what you edit.
- Is it new? An alert should only fire for breakage that wasn't there at the last check. If you get the full list every day, people stop reading by day three.
Keep those four in mind as you choose a setup, because the main difference between the options below is how much of this you get without extra work.
Option 1: A checker with a built-in Slack integration
Some link-monitoring tools connect to Slack directly. You authorize the app, pick a channel, and you're done. This is the least work if your tool offers it.
Things to check before you rely on it:
- Does it send only new breakage, or the full list every run? Ask, or trigger a test and wait for a second run.
- Can you route different sites to different channels? Agencies usually want one channel per client, or at least one for production sites and one for staging.
- Does it re-verify before alerting? A link that times out once at 3 a.m. isn't necessarily broken. Good checkers recheck failures before reporting them.
If your checker has no native Slack app but does offer webhooks, read on.
Option 2: Generic webhook → small relay → Slack incoming webhook
Many monitoring tools, DeadLinkPatrol included, send a generic JSON webhook when something breaks. You can't paste that webhook straight into Slack. Slack's incoming webhooks expect a specific payload with a text field (or Block Kit blocks), and Slack's documentation lists no_text as the error you get when text is missing. A tool's own payload format won't match.
The fix is a small relay: a function that receives the tool's webhook, checks that it's genuine, reformats it, and forwards it to Slack. It's about 40 lines of code and can run on a free serverless tier.
Step 1: Create the Slack incoming webhook
In Slack's app settings, create an app (or use an existing one), turn on Incoming Webhooks, click Add New Webhook to Workspace, and pick the channel. You'll get a URL like https://hooks.slack.com/services/T000/B000/XXXX. Treat it as a secret. Slack's docs say it actively looks for leaked webhook URLs and revokes them.
Step 2: Deploy the relay
Here's a relay written as a Cloudflare Worker. It expects the DeadLinkPatrol payload format, where the links.broken event carries site, newBroken (each item has url, status, kind and foundOn), totalBroken and reportUrl. It also checks the X-DeadLinkPatrol-Signature header, which looks like t=<unix time>,v1=<hex HMAC-SHA256 of "t.body">. If you use a different tool, change the field names and the signature check to match its docs.
// Cloudflare Worker: DeadLinkPatrol webhook → Slack incoming webhook
// Secrets: DLP_SECRET (your webhook secret), SLACK_URL (incoming webhook URL)
const enc = new TextEncoder();
async function hmacHex(secret, msg) {
const key = await crypto.subtle.importKey(
'raw', enc.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
const sig = await crypto.subtle.sign('HMAC', key, enc.encode(msg));
return [...new Uint8Array(sig)].map(b => b.toString(16).padStart(2, '0')).join('');
}
export default {
async fetch(req, env) {
if (req.method !== 'POST') return new Response('ok');
const raw = await req.text();
const header = req.headers.get('x-deadlinkpatrol-signature') || '';
const parts = Object.fromEntries(header.split(',').map(p => p.split('=')));
if (!parts.t || Math.abs(Date.now() / 1000 - Number(parts.t)) > 300)
return new Response('stale or missing timestamp', { status: 400 });
if (await hmacHex(env.DLP_SECRET, parts.t + '.' + raw) !== parts.v1)
return new Response('bad signature', { status: 401 });
const p = JSON.parse(raw);
let text;
if (p.event === 'links.broken') {
const lines = p.newBroken.slice(0, 10).map(b =>
`• ${b.status || 'ERR'} ${b.url}\n on ${b.foundOn?.[0] || 'unknown page'}`);
const more = p.newBroken.length > 10 ? `\n…and ${p.newBroken.length - 10} more` : '';
text = `:warning: ${p.newBroken.length} new broken link(s) on ${p.site} ` +
`(${p.totalBroken} total)\n${lines.join('\n')}${more}\n<${p.reportUrl}|Open report>`;
} else {
text = `DeadLinkPatrol: ${p.message || p.event}`;
}
const r = await fetch(env.SLACK_URL, {
method: 'POST', headers: { 'content-type': 'application/json' },
body: JSON.stringify({ text }) });
return new Response(r.ok ? 'sent' : 'slack error ' + r.status, { status: r.ok ? 200 : 502 });
}
};
A few notes on the design:
- Signature check. Without it, anyone who finds your relay URL can post fake alerts to your channel. The five-minute timestamp window blocks replayed requests.
- Ten-line cap. After a site migration a single run can surface hundreds of breaks. Slack messages have size limits, and nobody reads a wall of 300 URLs anyway. Link to the full report.
- One relay per channel, or add a lookup that maps
p.siteto different Slack URLs, if you want a channel per client.
Step 3: Point your checker at the relay
Paste the Worker's URL into your link checker's webhook setting and send a test event. In DeadLinkPatrol, webhooks are on paid plans, and there's a test button that sends {"event":"test", ...}. The relay above handles that event, so you should see a message in Slack within seconds.
Option 3: Slack Workflow Builder webhook trigger
Slack also lets a workflow start from a webhook. Workflow Builder gives you a URL under hooks.slack.com/triggers/..., you define variables, and any request whose JSON keys match those variables starts the workflow. The workflow can then post a formatted message, open a ticket in a connected app, or tag a person.
The trade-offs:
- Paid Slack plan only. Workflow Builder isn't available on Slack's free plan.
- Flat JSON only. Slack's docs say webhook triggers take top-level variables and don't support nested JSON. A payload with an array of broken links (like
newBrokenabove) can't be mapped item by item. You can still use flat fields such as the site and the report URL, but the per-link detail is gone, and that detail is what makes an alert worth reading. - No signature checks. Anyone who has the trigger URL can start the workflow.
Workflow Builder works well for "send the team a heads-up with a link to the report." If you want the broken URLs in the message itself, use the relay.
Option 4: Zapier, Make, or n8n
No-code automation tools sit in the same place as the relay. They catch the webhook, let you pick fields, and post to Slack. They're quick to set up, but:
- Free tiers usually cap tasks per month, and an agency with 30 sites can use those up during a bad week.
- Looping over an array of broken links usually needs an extra step (an iterator in Make, a code step or line-item handling in Zapier).
- Most of them won't verify the webhook signature unless you add a code step.
If you already pay for one of these tools for other work, it's a reasonable choice. If not, the relay is free and you control it.
Keeping the channel quiet enough that people read it
A broken-link channel only works if people trust that every message needs action. Some rules that help:
Alert only on new breakage. This is the most important rule. Your checker should compare each run with the previous one and only notify about the difference. If yours can't, put a small store (Workers KV, a JSON file, a sheet) in the relay and diff the lists yourself.
Re-verify before alerting. External sites rate-limit, time out, and go down for maintenance. Check a failure twice, a few minutes apart, before calling it broken.
Treat 403, 429 and 999 as "verify by hand", not "broken". Social networks and some large sites answer automated requests with these codes while working fine in a browser. Keep them out of the alert channel, or label them clearly.
Separate internal from external. A broken internal link is always yours to fix. A broken external link might mean editing your page, or it might mean the other site is down for an hour. Some teams send external-link alerts to a weekly digest instead of the live channel.
Thread the fix. When someone fixes a link, they reply in the thread with what they did ("301 from /old-pricing to /pricing"). Six months later, that thread tells you why the redirect exists.
Per-client channels for agencies
If you look after client sites, alert routing is where Slack really helps:
- One channel per client (
#client-acme-alerts), with alerts from every Acme property routed there. - A shared
#all-site-alertschannel for the on-call person, with only internal 404s and server errors. - Monthly, export the month's alert history into your client report. "We caught and fixed 14 broken links this month" is concrete evidence of what the care plan pays for.
DeadLinkPatrol's Agency plan ($79/mo) covers 50 sites with daily scans and white-label client reports. If you want branded monthly health reports that also cover SSL, uptime and SEO basics, AgencyHealth starts at $49/mo for 50 sites. On either one, webhooks carry the same signed payload the relay above expects. Compare plans.
Quick checklist
- Decide which channel(s) get alerts and who owns each one
- Create the Slack incoming webhook (keep the URL secret)
- Deploy a relay that checks the signature and formats the message
- Point your checker's webhook at the relay and send a test event
- Confirm alerts only fire for new breakage
- Keep bot-blocked status codes (403/429/999) out of the live channel
- Agree that fixes get logged in the alert's thread
Want to see what an alert would contain before setting any of this up? Run a free scan of up to 25 pages. No signup needed, and you'll see the exact broken URLs and the pages they're on.