A Netlify deploy webhook fires when the build dies. A Netlify Function that throws at runtime is a different failure: the site stays green, /api/checkout returns 500, and nobody looks at Functions → Logs until a customer complains.
You do not need a Slack SDK inside every function. Catch the exception, POST AlertHub, then Slack, Telegram, WhatsApp, Discord, or Teams all receive the same critical event.
This is different from Netlify deploy notifications (outgoing webhooks on build status). Here the function itself reports that it crashed.
What you should alert on
| Event | Where | Suggested level | Why it matters |
|---|---|---|---|
| Uncaught exception in an HTTP function | try/catch around the handler | Critical | Checkout, auth, or a webhook you own just died |
| Scheduled function failed | same try/catch on the cron handler | Critical | Nightly job did not run |
| Downstream API 4xx you already handle | explicit sendAlert | Warning | Recoverable; still worth a dashboard row |
| Successful invocation | omit, or info | Info | History only — do not ping Slack |
Only warning and critical are pushed to external channels. Successful calls should stay info (or be omitted).
Setup (once)
- Create a project at app.thealerthub.com and copy the secret key.
- In Netlify: Project configuration → Environment variables → add
ALERTHUB_KEY. Scope it to Production (and Preview if you want PR noise). Never put the key in the function source. - Connect destinations in AlertHub. Telegram is enough for a solo maintainer; Slack for the team channel.
Docs: Ingestion API. Deploy-failed webhooks (not function crashes): Netlify source.
Alert 1: HTTP function crashed (the default)
Functions 2.0 export a default handler. Wrap the work in try/catch, notify AlertHub, then return 500. Swallow errors from the notify call so a down AlertHub does not hide the original stack.
// netlify/functions/checkout.mjs
const ALERTHUB_URL = 'https://api.thealerthub.com/sendAlert';
async function notifyAlertHub({ title, description, level = 'critical' }) {
try {
await fetch(ALERTHUB_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
ah_key: process.env.ALERTHUB_KEY,
ah_title: title,
ah_description: description,
ah_level: level,
ah_source: 'Netlify Function',
}),
});
} catch (notifyError) {
console.error('AlertHub notify failed', notifyError);
}
}
export default async (req, context) => {
try {
const body = await req.json();
const result = await chargeCustomer(body);
return Response.json({ ok: true, result });
} catch (error) {
const stack = error instanceof Error ? error.stack : String(error);
await notifyAlertHub({
title: 'Netlify Function failed: checkout',
description: `${stack}\nurl: ${req.url}\nrequestId: ${context.requestId}`,
});
return new Response('Internal error', { status: 500 });
}
};
export const config = { path: '/api/checkout' };
Deploy, force a throw (bad payload is enough), then confirm the alert in the dashboard and on your channels.
Alert 2: Classic exports.handler
Older functions still use the Lambda-compatible API. Same env var, same JSON body:
// netlify/functions/legacy-webhook.js
const ALERTHUB_URL = 'https://api.thealerthub.com/sendAlert';
exports.handler = async (event) => {
try {
await processWebhook(JSON.parse(event.body || '{}'));
return { statusCode: 200, body: '{"ok":true}' };
} catch (error) {
try {
await fetch(ALERTHUB_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
ah_key: process.env.ALERTHUB_KEY,
ah_title: 'Netlify Function failed: legacy-webhook',
ah_description: error instanceof Error ? error.stack : String(error),
ah_level: 'critical',
ah_source: 'Netlify Function',
}),
});
} catch (notifyError) {
console.error('AlertHub notify failed', notifyError);
}
return { statusCode: 500, body: 'Internal error' };
}
};
Alert 3: Scheduled function
Cron functions fail silently if you only look at the site. Same catch, ah_source set so the feed stays filterable:
// netlify/functions/nightly-sync.mjs
export default async () => {
try {
await syncOrders();
return new Response('ok');
} catch (error) {
await fetch('https://api.thealerthub.com/sendAlert', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
ah_key: process.env.ALERTHUB_KEY,
ah_title: 'Netlify scheduled function failed: nightly-sync',
ah_description: error instanceof Error ? error.stack : String(error),
ah_level: 'critical',
ah_source: 'Netlify Cron',
}),
}).catch((notifyError) => console.error('AlertHub notify failed', notifyError));
return new Response('Internal error', { status: 500 });
}
};
export const config = { schedule: '0 3 * * *' };
Ideas to squeeze more value
- Reuse one helper. Copy
notifyAlertHubintonetlify/functions/_lib/alerthub.mjsand import it from every handler. - Include
requestId. The description above opens the matching row in Netlify function logs in one search. - Preview vs production. Use a second AlertHub project (or
warningon Deploy Previews) so a broken PR does not page the on-call channel. - Edge Functions. Same
try/catch+fetch. Read the key withNetlify.env.get('ALERTHUB_KEY')(orDeno.env.get) — still an env var, never a hardcodedah_key. - Combine with deploy webhooks. Build failures via Netlify deploys; runtime crashes via the snippets above.
FAQ
Why not only Netlify function logs / email?
Logs need someone to open them. Email from Netlify drowns next to newsletters. AlertHub is the last hop so Slack, Telegram, and WhatsApp stay in sync.
Does process.env.ALERTHUB_KEY work in Functions 2.0?
Yes. Set it in the Netlify UI (or netlify env:set ALERTHUB_KEY). Redeploy after adding the variable so production picks it up.
Can I send info on success?
Yes, with "ah_level": "info". It will not fan out to channels.
Is this the same as a failed Netlify deploy?
No. Deploy webhooks cover build/publish. This covers exceptions inside a function after the site is already live. Use both.
Next steps
Add ALERTHUB_KEY, paste the try/catch into one production function, force a throw, then connect Slack or Telegram.
Related: Netlify deployments · Cloudflare Worker failures · Ingestion API