Adding an n8n Widget to Homepage with Secure Metrics

Posted on Jan 10, 2026

Why I Wanted n8n Metrics on Homepage

When running a small homelab, visibility matters more than dashboards.
I don’t want Grafana for everything — I just want to know at a glance:

  • how many workflows exist
  • how busy n8n has been today
  • whether something failed recently

Homepage already acts as my control panel, so the natural question was:

Can I add a real n8n widget without exposing the n8n API?

Short answer: yes, using a custom API widget and a secured n8n webhook.

This post shows exactly how.


High-level Architecture

The idea is simple:

  1. Homepage calls a custom API endpoint
  2. That endpoint is an n8n Webhook
  3. The webhook:
    • queries n8n internals (workflows + executions)
    • returns a small JSON payload
  4. Homepage renders that JSON as a widget

What We Will Build

homepage n8n widget

Final widget metrics:

  • Workflows → total workflows
  • Executions (24h) → executions started in last 24h
  • Failed (24h) → executions failed in last 24h

All behind:

  • Docker
  • A single webhook
  • Header-based authentication

Step 1 — Create the Webhook Entry Point in n8n

Create a new workflow in n8n and add a Webhook node.

Configuration:

  • HTTP Method: GET
  • Respond: Using Respond to Webhook node
  • Authentication: Header Auth

Header Auth configuration:

  • Header Name: X-Widget-Token
  • Header Value: a random token of your choice

Activate the workflow.
Copy the Production URL (/webhook/..., not /webhook-test/...).


Step 2 — Count Workflows

Add an n8n → Get many workflows node.

  • Return All: false
  • Limit: 250

Then add a Code node:

return [
  {
    json: {
      workflows: $input.all().length,
    },
  },
];

Step 3 — Count Executions in the Last 24h (Including Failures)

Add a second branch from the Webhook:

  • n8n → Get many executions
    • Return All: false
    • Limit: 250
    • Include Data: false

Then add this Code node:

const cutoff = Date.now() - 24 * 60 * 60 * 1000;
const items = $input.all();

let executions24h = 0;
let failed24h = 0;

for (const it of items) {
  const e = it.json;

  const ts = Date.parse(
    e.startedAt || e.stoppedAt || e.createdAt || ""
  );

  if (Number.isNaN(ts) || ts < cutoff) continue;

  executions24h += 1;

  if (e.status === "error") {
    failed24h += 1;
  }
}

return [
  {
    json: {
      executions_24h: executions24h,
      executions_failed_24h: failed24h,
    },
  },
];

Step 4 — Merge and Respond

Add a Merge node:

  • Mode: Combine
  • Combine By: Position
  • Output: Both inputs merged together

Then add Respond to Webhook:

  • Respond With: First Incoming Item
  • Response Header:
    • Content-Type: application/json

Step 5 — Homepage Widget Configuration

widget:
  type: customapi
  url: {{HOMEPAGE_VAR_N8N_WEBHOOK_URL}}
  method: GET
  headers:
    X-Widget-Token: {{HOMEPAGE_VAR_N8N_WIDGET_TOKEN}}
  refreshInterval: 60000
  mappings:
    - field: workflows
      label: Workflows
      format: number
    - field: executions_24h
      label: Executions (24h)
      format: number
    - field: executions_failed_24h
      label: Failed (24h)
      format: number

Security Notes

🚨 Alert
Do not expose the n8n API directly to Homepage

This approach:

  • exposes only a minimal JSON
  • keeps API keys private
  • cleanly rejects unauthorized requests