Push your first metric: a practical guide to dashboard webhooks
The screen is the easy part. Picking a TV and putting a browser in kiosk mode takes an afternoon — we walked through it in how to show live metrics on an office TV. The question that actually decides whether a metrics wall survives its first month is a different one: how does the number get onto the screen? For almost every metric, the answer is a webhook. Here's what that means in practice, the four ways to feed one, and the mistakes that cost people an afternoon.
A metric webhook is one URL and one POST
Every number on the wall gets its own URL. Updating that number means sending an HTTP POST to it with a small JSON body — no SDK to install, no library to keep up to date, nothing polling your database on a schedule you don't control.
# Send the latest revenue to a tile
curl -X POST \
https://api.dashwall.io/api/metric/your-tile-token \
-H 'Content-Type: application/json' \
-d '{"value": 128940}'That's the entire protocol. The token in the URL is both the address and the authentication: it identifies which tile the value belongs to, so there's no API key to exchange and no login step. Anything that can make an HTTP request can drive the wall — your backend, a shell script, a GitHub Action, an automation tool, a spreadsheet add-on. With Dashwall the posted value reaches every screen showing that board in under a second, which is the part the how it works page covers in more detail.
Four ways to get the number out of your system
Pick one per metric, not one per company — most walls end up mixing two or three of these.
- From your own code, when something happens. A sale is recorded, a signup completes, a deploy finishes: post the new value right there. It's a few lines next to code you already have, and it's the most accurate option because it fires at the moment of truth.
- From a scheduled job. Some numbers aren't events at all — open tickets, monthly recurring revenue, users online right now. There's nothing to hook into, so a cron job or a scheduled GitHub Action that queries and posts every few minutes is the right tool. A wall read from across the room gains nothing from second-level freshness.
- From a no-code automation. Zapier, Make, and n8n all have a generic "POST to a webhook" step. Trigger on a new Stripe payment or a new CRM deal, map the amount into the body, done — no repository, no deploy, no ops.
- From an AI agent. If you'd rather describe the metric than wire it up, an agent can create the tiles and push the values for you — see let an AI agent build your KPI dashboard.
// After a payment is recorded, mirror the new total on the wall.
await fetch(`https://api.dashwall.io/api/metric/${process.env.DASHWALL_REVENUE_TILE}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ value: await revenueToday() }),
});Post the value you want to see, not the change
This is the mistake that catches almost everyone once. A €250 sale comes in, and the instinct is to post 250. But the tile shows what you send it — it isn't a counter you add to. Send the new total instead, and keep the running total where the truth already lives: in your database.
The reason goes beyond correctness. An absolute value is idempotent: if a scheduled job runs twice, a retry fires, or a queue redelivers a message, posting "today's revenue is 128940" twice is harmless. Posting "add 250" twice quietly inflates the number on the wall, and nobody notices until someone asks why the dashboard and the finance report disagree. Treat the wall as a mirror, never as a ledger — a missed update simply gets corrected by the next one.
Send a number, not a formatted number
The body should carry {"value": 1204.5}, not "€1,204.50". Numeric strings like "1204.5" are fine — that keeps shell scripts and automation tools that stringify everything working — but currency symbols, thousands separators, and percent signs are not numbers, and a tile can't animate to them.
Formatting is a property of the tile, not of the integration: the unit (€, %, #) and the number of decimals are settings on the board. That separation is worth protecting. When you decide next month to show revenue in thousands, you change a tile setting — not a deploy of the system that posts the value.
Three more things that trip people up
- Respect the rate limit. Dashwall accepts 60 updates per minute per tile, which is far more than a human eye can use. If your events come in faster — a busy checkout, a firehose of log lines — aggregate them and post once every few seconds. Go over and you'll get a
429back with aRetry-Afterheader telling you exactly how long to wait. - Treat the webhook URL as a credential. The token is the only thing standing between your wall and a stranger's numbers. Keep it server-side: an environment variable, your automation tool's secret store, your CI's secrets. Never put it in client-side JavaScript on a public page, where anyone can read it out of the bundle.
- Never let the wall break the app. The POST that updates a tile should be fire-and-forget, or better, a job on a queue. No checkout should ever fail because a metrics endpoint was slow. Wrap it, ignore the failure, move on — the next update fixes the display anyway.
Test it before it goes on the wall
Post one value by hand with the curl command above and watch the tile count up. That single round trip proves the whole chain — token, body, board, screen — in about ten seconds, and it's much easier to debug from a terminal than from a TV in the hallway. Once real values start flowing, the history builds up behind the tile (Dashwall keeps the last 500 points), so a sparkline shows the trend and a milestone can celebrate the moment you cross a target.
That's the whole integration
One URL per number, one POST when it changes, absolute values, no formatting. That's the entire contract between your systems and the wall — small enough to add during a coffee break, and boring enough to keep working while everyone forgets it's there. Dashwall is launching soon: join the waitlist and your first tile will come with its webhook URL ready to copy.