Un throw sin capturar en un Cloudflare Worker se convierte en Error 1101 para el visitante. El gráfico del Worker en el dashboard se pone rojo más tarde. El email de Cloudflare se pasa por alto. Los equipos buscan notificaciones Slack de Cloudflare Workers porque quieren la excepción donde ya hablan.
No necesitas un SDK de Slack en el edge. Captura la excepción, POST a AlertHub (clave en un secreto del Worker), y Slack, Telegram, WhatsApp, Discord o Teams reciben el mismo evento critical.
Usa ctx.waitUntil() para el webhook: el 500 sale enseguida y el notify aún termina. No desestructures waitUntil de ctx — lanza Illegal invocation.
Qué deberías alertar
| Evento | Handler del Worker | Nivel recomendado | Por qué importa |
|---|---|---|---|
Excepción no controlada en fetch | try/catch + ctx.waitUntil | Critical | La ruta de edge o el Worker API ha muerto |
Cron (scheduled) fallido | el mismo catch en el handler cron | Critical | El job nocturno no corrió |
| 5xx del origin que ya manejas | notify explícito | Warning | El origin parpadeó; el Worker siguió |
| Petición correcta | omitir, o info | Info | Solo historial — no pings a Slack |
Solo warning y critical se empujan a canales externos. Las peticiones correctas deben quedarse en info (o omitirse).
Configuración (una sola vez)
- Crea un proyecto en app.thealerthub.com y copia la clave secreta.
- Guárdala como secreto del Worker, no en
[vars]dewrangler.toml:
npx wrangler secret put ALERTHUB_KEY
En local, pon ALERTHUB_KEY=... en .dev.vars (en .gitignore). El handler lee env.ALERTHUB_KEY.
- Conecta destinos en AlertHub. Telegram basta para un maintainer; Slack para el canal del equipo.
Docs: API de ingestión. El mismo patrón en Netlify: fallos de Netlify Functions.
Alerta 1: Handler fetch petado (el valor por defecto)
Envuelve el Worker en try/catch. Pasa la promesa del notify a ctx.waitUntil y encadena .catch() para que un fallo de AlertHub no marque la invocación como excepción.
const ALERTHUB_URL = 'https://api.thealerthub.com/sendAlert';
function notifyAlertHub(env, { title, description, level = 'critical' }) {
return fetch(ALERTHUB_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
ah_key: env.ALERTHUB_KEY,
ah_title: title,
ah_description: description,
ah_level: level,
ah_source: 'Cloudflare Worker',
}),
}).then((res) => {
if (!res.ok) throw new Error(`AlertHub ${res.status}`);
}).catch((notifyError) => {
console.error('AlertHub notify failed', notifyError);
});
}
export default {
async fetch(request, env, ctx) {
try {
const data = await handleRequest(request, env);
return Response.json(data);
} catch (error) {
const stack = error instanceof Error ? error.stack : String(error);
ctx.waitUntil(
notifyAlertHub(env, {
title: 'Cloudflare Worker failed',
description: `${stack}\nurl: ${request.url}`,
}),
);
return new Response('Internal error', { status: 500 });
}
},
};
Despliega, pega una ruta que lance y confirma la alerta en el panel y en tus canales.
Alerta 2: Helper compartido que importas en todas partes
Queues, Durable Objects y otros fetch se copian mal. Un helper, y pasas env:
// src/alerthub.js
export function notifyAlertHub(env, error, extra = '') {
const stack = error instanceof Error ? error.stack : String(error);
return fetch('https://api.thealerthub.com/sendAlert', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
ah_key: env.ALERTHUB_KEY,
ah_title: 'Cloudflare Worker failed',
ah_description: extra ? `${stack}\n${extra}` : stack,
ah_level: 'critical',
ah_source: 'Cloudflare Worker',
}),
}).catch((notifyError) => console.error('AlertHub notify failed', notifyError));
}
import { notifyAlertHub } from './alerthub.js';
export default {
async fetch(request, env, ctx) {
try {
return await handleRequest(request, env);
} catch (error) {
ctx.waitUntil(notifyAlertHub(env, error, `url: ${request.url}`));
return new Response('Internal error', { status: 500 });
}
},
};
Alerta 3: Worker cron (scheduled)
Un cron fallido no devuelve HTTP 500 a un humano. Captúralo y avisa igual. No dejes que la promesa del notify rechace dentro de waitUntil.
import { notifyAlertHub } from './alerthub.js';
export default {
async scheduled(event, env, ctx) {
try {
await syncFromOrigin(env);
} catch (error) {
ctx.waitUntil(notifyAlertHub(env, error, `cron: ${event.cron}`));
throw error;
}
},
};
throw error mantiene honesto el dashboard de Cloudflare (invocación = excepción). Si prefieres dejar el cron en verde y paginar solo AlertHub, omite el rethrow.
Ideas para sacarle más partido
- Nunca pongas
ah_keyen el código ni en[vars]. Secretos conwrangler secret put; local con.dev.vars. - Deja
waitUntilenctx.const { waitUntil } = ctxpierdethisy tumba el Worker. - Incluye la URL, no las cookies. La descripción basta para grep en Workers Logs; el PII no pertenece a Slack.
- Preview vs production. Usa un segundo proyecto AlertHub (o
warning) en rutas que no sean producción para que un throw de staging no pagine a guardia. - Netlify Functions usan
process.env.ALERTHUB_KEYen lugar deenv— ver fallos de Netlify Functions.
Preguntas frecuentes
¿Por qué no solo Workers Logs / Tail?
Los logs necesitan que alguien los abra. Una sesión Tail se acaba. AlertHub es el último salto para que WhatsApp, Telegram y Discord sigan sincronizados.
¿Puedo usar process.env.ALERTHUB_KEY?
Solo si tienes compatibilidad Node.js. Prefiere env.ALERTHUB_KEY del binding de secreto — funciona en todos los handlers (fetch, scheduled, consumidores de colas).
¿Tengo que hacer await del fetch a AlertHub antes de devolver 500?
No. ctx.waitUntil(notify…) es el patrón correcto para no bloquear al cliente. Aun así encadena .catch() en esa promesa.
¿Puedo enviar info al éxito?
Sí, con "ah_level": "info". No se repartirá a canales.
Siguientes pasos
Ejecuta npx wrangler secret put ALERTHUB_KEY, pega el try/catch en fetch, fuerza un throw y conecta Slack o Telegram.
Relacionado: fallos de Netlify Functions · API de ingestión · enviar alertas desde Node