Skip to content

Notification Hub

The Notification Hub is the N8N workflow that centralises every notification of the infrastructure. It deduplicates alerts, applies quiet hours, routes to the correct channels, and delegates callbacks to a dedicated sub-workflow extracted during refactoring #276.

WorkflowNodesRole
Notification Hub (parent)41Dedup + quiet hours + format + multi-channel send
NH Callback Handler (9Xb3mplmzE5994bi)18Ack, approve, retry, ignore, delivery callbacks
SourceEvent types
Docker DIUNNew image versions, approval requests
AlertmanagerPrometheus alerts (CPU, RAM, disk)
Health CheckService status
Global Error HandlerClassified N8N errors with [Retry] [Details] [Ignore] [Fix] actions
Security CVE WatchCRITICAL alerts + weekly digest
N8N workflowsExecution results, business events
ChannelUsage
TelegramPrimary, interactive notifications with buttons
ntfyMobile push backup (CRITICAL channel)
DiscordOptional, for teams

Channels

Notification Hub · 41 nodes

No

Yes

NH Callback Handler · 18 nodes

Parse Callback

Ack

Approve

Retry

Ignore

Sources

Docker DIUN

Alertmanager

Health Check

Global Error Handler

Security CVE Watch

Various N8N workflows

Webhook / Execute Workflow

Is Callback?

Validate Payload

Deduplication · Redis

Quiet Hours

Format per Channel

Multi-channel Send

Insert History DT

Telegram · inline buttons

ntfy · mobile push

Discord


ProblemWithout hubWith hub
Notification spamEach service notifies independentlySingle controlled outbound point
DuplicatesSame alert sent multiple timesKey-based deduplication + TTL
Night-time alertsWoken up by a warningQuiet hours for non-criticals
No auditLost notificationsFull history in DB
Broken buttonsCallback logic scatteredReusable dedicated sub-workflow

The initial hub mixed notification ingestion and Telegram callback handling in a single 57-node workflow. Three symptoms triggered the extraction:

SymptomCause
Ambiguous double-triggerAn incoming webhook could be either a new notification OR a callback
Mishandled dedup bypassCallbacks should not pass through dedup but the code sometimes did so
Hard to testTesting a callback meant first sending a real notification

Extracting the NH Callback Handler sub-workflow (18 nodes, ID 9Xb3mplmzE5994bi) resolves these tensions: the parent routes early in the chain via Is Callback?, and callbacks bypass dedup + quiet hours.

FeatureBenefit
DeduplicationAvoids duplicate notifications inside a time window
Quiet hoursNon-critical notifications deferred 22:00-08:00
Multi-channelTelegram + ntfy + Discord depending on severity
HistoryEvery notification stored for audit
Routed callbacksBypass dedup for Telegram buttons, extracted sub-workflow

1. Reception — The hub receives via the /webhook/notification-hub webhook (internal only) or via Execute Workflow.

2. Is Callback? — Early detection: if the payload contains a Telegram callback_data, route to NH Callback Handler. Bypasses dedup + quiet hours.

3. Validation — Required type, valid severity (info / warning / critical), conformant JSON format.

4. Deduplication — Compute a <type>:<container>:<severity> key, lookup Redis, skip if present.

5. Quiet hours — If the time is between 22:00 and 08:00 AND severity is not critical, mark deferred=true and defer.

6. Format — Template per type and channel (Telegram HTML, Discord embed, ntfy text).

7. Multi-channel send — Telegram always, ntfy if critical, Discord if configured.

8. History — Insert into notification_history with dedup_key, channels, timestamp.

// Dedupe key
const dedupeKey = `${type}:${container}:${severity}`;
// TTL depending on severity
const ttl = {
info: 3600, // 1 hour
warning: 1800, // 30 min
critical: 300 // 5 min
};
const existing = await redis.get(dedupeKey);
if (existing) {
return { skipped: true, reason: 'duplicate' };
}
await redis.set(dedupeKey, Date.now(), { EX: ttl[severity] });
TimeInfoWarningCritical
08:00-22:00ImmediateImmediateImmediate
22:00-08:00QueuedQueuedImmediate

Deferred notifications are stored in a pending_notifications Data Table and dispatched on the next 08:00 pass via the Deferred Sender workflow.

Telegram callbacks (inline buttons) come back to the hub through the same webhook. The Is Callback? node (IF at the start of the chain) detects them via the presence of source='telegram_callback' or callback_query in the payload:

Yes

No

Webhook

source = telegram_callback ?

NH Callback Handler · 18n

Validate → Dedup → Quiet → Format → Send

Without this bypass, clicking [Retry] at 23:00 would either be deduplicated against the original notification or deferred until morning — which would break UX.

{
"type": "update_success",
"severity": "info",
"title": "Docker update succeeded",
"message": "n8n-stack updated to 1.73.0",
"container": "n8n",
"image": "n8nio/n8n:1.73.0",
"project": "n8n-stack",
"timestamp": "2026-01-20T10:30:00.000Z",
"callback_actions": [
{ "label": "Details", "callback": "notif_details_<id>" },
{ "label": "Ignore", "callback": "notif_ignore_<id>" }
],
"metadata": {
"duration_seconds": 45,
"previous_version": "1.72.0"
}
}
TypeDefault severityOrigin
update_success / update_failed / update_queuedinfo / critical / infoDocker Updates
approval_requestwarningDocker Updates, GEH Fix Applier
alert_prometheusvariableAlertmanager
container_downcriticalHealth Check
error_classifiedvariableGlobal Error Handler
cve_alertcriticalSecurity CVE Watch

From an N8N workflow (Execute Workflow):

{
"workflowId": {
"__rl": true,
"value": "<notification-hub-id>",
"mode": "id"
},
"inputData": {
"type": "update_success",
"severity": "info",
"title": "...",
"message": "..."
}
}

Via the internal webhook:

Fenêtre de terminal
curl -X POST http://n8n:5678/webhook/notification-hub \
-H "Content-Type: application/json" \
-d '{"type":"custom","severity":"info","title":"Test","message":"Hello"}'

The notification_history table (PostgreSQL N8N):

ColumnTypeDescription
idUUIDUnique identifier
typeTextNotification type
severityTextinfo / warning / critical
payloadJSONBFull content
channelsArrayChannels used
sent_atTimestampEffective sending time
deferredBooleanWhether deferred during quiet hours
dedupe_keyTextDedupe key
callback_resultsJSONBButton responses (cumulative)

LimitImpactMitigation
Single Redis instanceCache lost if Redis is downNotification still sent (no dedupe) — fail-open
No automatic escalationUnacknowledged alerts stay at warningPlanned but not implemented
Fixed quiet hoursNo per-user configurationSufficient for solo use
Callback timeoutIf the user never clicks, state stays pending7-day TTL on callbacks in the DT

If automatic escalation is needed:

  • Track unacknowledged alerts in pending_notifications
  • After a configurable delay, upgrade to critical → ntfy + optional Twilio call
  • Grafana dashboard with a heatmap of ignored alerts

If notification volume grows:

  • Batch aggregation (hourly summary for info)
  • Daily digest for non-urgent notifications
  • Subscription-based filtering (opt-in per type via Data Table)

If a multi-user team is involved:

  • Per-user quiet-hour configuration (notification_routing Data Table)
  • Routing based on the on-call user
  • Shared acknowledgements (a single ack is enough)

  • Glossary — Webhook, Deduplication, Quiet Hours