--- title: Notification Hub url: https://blog.guigpap.com/en/workflows/notification-hub/ url_md: https://blog.guigpap.com/en/workflows/notification-hub.md category: automation date: '2026-01-20' maturite: production techno: - n8n - telegram application: - monitoring - operations --- # Notification Hub > Central workflow for smart notification routing with deduplication, quiet hours and a callback sub-workflow ## 1. What? — Definition and context 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. > **Note - Central hub** > > Without a central hub, every service emits its own notifications. Result: spam, duplicates, 3 AM alerts for non-critical warnings. The Notification Hub solves these issues by being the single outbound point for every alert. ### Components | Workflow | Nodes | Role | |----------|-------|------| | **Notification Hub** (parent) | 41 | Dedup + quiet hours + format + multi-channel send | | **NH Callback Handler** (`9Xb3mplmzE5994bi`) | 18 | Ack, approve, retry, ignore, delivery callbacks | ### Notification sources | Source | Event types | |--------|-------------| | **Docker DIUN** | New image versions, approval requests | | **Alertmanager** | Prometheus alerts (CPU, RAM, disk) | | **Health Check** | Service status | | **Global Error Handler** | Classified N8N errors with [Retry] [Details] [Ignore] [Fix] actions | | **Security CVE Watch** | CRITICAL alerts + weekly digest | | **N8N workflows** | Execution results, business events | ### Output channels | Channel | Usage | |---------|-------| | **Telegram** | Primary, interactive notifications with buttons | | **ntfy** | Mobile push backup (CRITICAL channel) | | **Discord** | Optional, for teams | ### Visual architecture ```mermaid flowchart TD subgraph Sources["Sources"] direction TB DIUN["Docker DIUN"] AM["Alertmanager"] HC["Health Check"] GEH["Global Error Handler"] CVE["Security CVE Watch"] WFs["Various N8N workflows"] end subgraph Hub["Notification Hub · 41 nodes"] direction TB Receive["Webhook / Execute Workflow"] IsCB["Is Callback?"] Validate["Validate Payload"] Dedup["Deduplication · Redis"] Quiet["Quiet Hours"] Format["Format per Channel"] Send["Multi-channel Send"] History["Insert History DT"] end subgraph CB["NH Callback Handler · 18 nodes"] direction TB Parse["Parse Callback"] AckBtn["Ack"] ApproveBtn["Approve"] RetryBtn["Retry"] IgnoreBtn["Ignore"] end subgraph Out["Channels"] direction TB TG["Telegram · inline buttons"] NTFY["ntfy · mobile push"] Discord["Discord"] end Sources --> Receive --> IsCB IsCB -->|Yes| CB IsCB -->|No| Validate --> Dedup --> Quiet --> Format --> Send Send --> TG Send --> NTFY Send --> Discord Send --> History ``` --- ## 2. Why? — Stakes and motivations ### Problems solved | Problem | Without hub | With hub | |---------|-------------|----------| | **Notification spam** | Each service notifies independently | Single controlled outbound point | | **Duplicates** | Same alert sent multiple times | Key-based deduplication + TTL | | **Night-time alerts** | Woken up by a warning | Quiet hours for non-criticals | | **No audit** | Lost notifications | Full history in DB | | **Broken buttons** | Callback logic scattered | Reusable dedicated sub-workflow | ### Why extract a Callback Handler (#276)? The initial hub mixed notification ingestion and Telegram callback handling in a single 57-node workflow. Three symptoms triggered the extraction: | Symptom | Cause | |---------|-------| | **Ambiguous double-trigger** | An incoming webhook could be either a new notification OR a callback | | **Mishandled dedup bypass** | Callbacks should not pass through dedup but the code sometimes did so | | **Hard to test** | Testing 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. ### Key features | Feature | Benefit | |---------|---------| | **Deduplication** | Avoids duplicate notifications inside a time window | | **Quiet hours** | Non-critical notifications deferred 22:00-08:00 | | **Multi-channel** | Telegram + ntfy + Discord depending on severity | | **History** | Every notification stored for audit | | **Routed callbacks** | Bypass dedup for Telegram buttons, extracted sub-workflow | --- ## 3. How? — Technical implementation ### A notification's journey **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 `::` 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. ### Deduplication ```javascript // 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] }); ``` ### Quiet hours | Time | Info | Warning | Critical | |------|------|---------|----------| | 08:00-22:00 | Immediate | Immediate | Immediate | | 22:00-08:00 | Queued | Queued | Immediate | Deferred notifications are stored in a `pending_notifications` Data Table and dispatched on the next 08:00 pass via the `Deferred Sender` workflow. ### Callback bypass 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: ```mermaid flowchart TD Receive["Webhook"] IsCB{"source = telegram_callback ?"} Direct["NH Callback Handler · 18n"] Normal["Validate → Dedup → Quiet → Format → Send"] Receive --> IsCB IsCB -->|Yes| Direct IsCB -->|No| Normal ``` Without this bypass, clicking `[Retry]` at 23:00 would either be deduplicated against the original notification or deferred until morning — which would break UX. ### Input format ```json { "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_" }, { "label": "Ignore", "callback": "notif_ignore_" } ], "metadata": { "duration_seconds": 45, "previous_version": "1.72.0" } } ``` ### Supported types | Type | Default severity | Origin | |------|------------------|--------| | `update_success` / `update_failed` / `update_queued` | info / critical / info | Docker Updates | | `approval_request` | warning | Docker Updates, GEH Fix Applier | | `alert_prometheus` | variable | Alertmanager | | `container_down` | critical | Health Check | | `error_classified` | variable | Global Error Handler | | `cve_alert` | critical | Security CVE Watch | ### Calling the hub **From an N8N workflow (Execute Workflow):** ```javascript { "workflowId": { "__rl": true, "value": "", "mode": "id" }, "inputData": { "type": "update_success", "severity": "info", "title": "...", "message": "..." } } ``` **Via the internal webhook:** ```bash curl -X POST http://n8n:5678/webhook/notification-hub \ -H "Content-Type: application/json" \ -d '{"type":"custom","severity":"info","title":"Test","message":"Hello"}' ``` > **Caution - Internal webhook only** > > The `/webhook/notification-hub` webhook is blocked by Caddy from the public Internet. It is only reachable through the internal `webproxy` Docker network. Any public exposure would introduce an abuse vector. ### History The `notification_history` table (PostgreSQL N8N): | Column | Type | Description | |--------|------|-------------| | `id` | UUID | Unique identifier | | `type` | Text | Notification type | | `severity` | Text | info / warning / critical | | `payload` | JSONB | Full content | | `channels` | Array | Channels used | | `sent_at` | Timestamp | Effective sending time | | `deferred` | Boolean | Whether deferred during quiet hours | | `dedupe_key` | Text | Dedupe key | | `callback_results` | JSONB | Button responses (cumulative) | --- ## 4. What if? — Outlook and limits ### Current limits | Limit | Impact | Mitigation | |-------|--------|------------| | **Single Redis instance** | Cache lost if Redis is down | Notification still sent (no dedupe) — fail-open | | **No automatic escalation** | Unacknowledged alerts stay at warning | Planned but not implemented | | **Fixed quiet hours** | No per-user configuration | Sufficient for solo use | | **Callback timeout** | If the user never clicks, state stays pending | 7-day TTL on callbacks in the DT | ### Evolution scenarios **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) --- ## Related pages ### Infrastructure - [N8N in queue mode](/en/infrastructure/n8n-queue-mode/) — Backend automation - [Monitoring Stack](/en/infrastructure/monitoring-stack/) — Prometheus source - [Notify Stack](/en/infrastructure/notify-stack/) — DIUN and ntfy ### Workflows - [Telegram Orchestrator](/en/workflows/telegram-orchestrator/) — Callback receiver - [Docker Updates](/en/workflows/docker-updates/) — DIUN source + approval - [Error Handler](/en/workflows/error-handler/) — Notifies via Hub with action buttons ### Reference - [Glossary](/en/reference/glossary/) — Webhook, Deduplication, Quiet Hours ## Metadonnees agent - Cet article est issu du blog GuiGPaP Lab. - Contexte global du blog: https://blog.guigpap.com/llms.txt - Contact auteur: https://odoo.guigpap.com/mon-cv - Licence: CC-BY-SA 4.0