Reporting & dashboards
Auto-alert on a sudden drop in sales
When checkout breaks or an ad set gets paused, orders don't error out — they just stop, and the store looks fine until the next morning's numbers come in low. This watches the order flow in near-real-time, learns what a normal Tuesday 2pm looks like for your store, and pings a Slack channel within the hour when sales fall off a cliff against that baseline — with a triage checklist so someone can go look before the lost revenue stacks up. Build it yourself with the steps below, or we build it on your own accounts — the baseline, the sustained-breach logic, the cooldown so it never spams you — and hand you the keys so you own it with no monthly fee. Free teardown of where your store actually goes quiet first.
- Shopify
- Slack
The manual way today
Nobody is assigned to watch sales minute by minute, so a sudden drop gets discovered by accident. Someone notices the order-notification Slack went quiet and wonders if that's normal. A customer DMs to say the 'Pay now' button is throwing an error. The ads person opens Meta and realizes a campaign got rejected or hit its budget cap hours ago. On a bad day, the first real signal is the next-morning revenue digest reading suspiciously low — by which point the break has been live overnight. Until then, 'is something wrong?' is answered by gut feel, because the store doesn't actually tell you when orders stop.
The cost isn't the drop — it's the gap between when it started and when a human noticed, and that gap is usually measured in hours. A checkout error during a peak window is pure lost revenue you never get back, and if ads are still running, you're paying full price to send traffic into a funnel that can't convert. The quiet failures are the expensive ones precisely because nothing alarms: there's no error in your inbox, no failed-payment report, just a number that comes in low long after you could have fixed it. The bigger tax is the low-grade anxiety of not knowing — refreshing the dashboard 'just to check,' which is its own kind of busywork that still doesn't catch the 9pm outage.
What running looks like
Every 15 minutes, the trailing hour of orders gets checked against what's normal for that exact hour of the week — so a slow Sunday morning is judged against past Sunday mornings, not against Friday peak. When sales fall below your threshold and stay there long enough to be real, a clear alert lands in a Slack channel: what you're doing versus what you'd expect right now, how far below, and a short checklist of the usual culprits to go check. It only fires once per incident — no 15-minute drip during a three-hour outage — and posts an all-clear the moment sales climb back to normal, so the channel stays trustworthy enough that people actually look when it pings.
01The build
How to build it
Define what 'a drop' means for your store — on paper first
Needs a humanAn anomaly alert is only as good as the rule behind it, so write the rule down before building anything. Pick the signal: orders per hour for most stores (revenue per hour is noisier — one big order swings it). Pick the threshold as a relative fall, not an absolute number — for example 'fewer than 40% of the orders expected for this hour' — so the same logic works whether you do 10 orders a day or 1,000. Pick the sustained window that separates a real break from a quiet patch: a single empty 15 minutes is noise; 30–45 minutes of near-zero against a busy hour is a signal. And name your 'floor' hours — the genuinely dead overnight window where expected volume is so low that a drop is meaningless — so the alert never fires at 4am about the two orders that didn't come.
Build an hour-of-week baseline from your trailing history
Runs itselfYou can't detect 'below normal' without a definition of normal, and 'normal' for DTC is hour-specific: Tuesday 2pm and Tuesday 2am are different stores. On a daily schedule, pull the last 4 weeks of orders from the Shopify Admin API and bucket them into the 168 hours of the week (Mon 00:00 … Sun 23:00) in your store's timezone. For each bucket compute the median orders and median net sales — median, not mean, so one viral day or a past flash sale doesn't permanently inflate the 'expected' line. Store the table in a Google Sheet, Airtable, or n8n static data. This 168-row baseline is the thing every live check compares against, and refreshing it daily lets it track real growth instead of accusing a bigger store of dropping.
Run the live check on a tight schedule
Runs itselfUse an n8n Schedule Trigger set to every 15 minutes. Build the trailing-60-minute window from the run time in your store's timezone, formatted as ISO 8601 with the offset (for example 2026-06-29T13:15:00-07:00 to 2026-06-29T14:15:00-07:00), and derive the offset from the timezone so it doesn't drift an hour when daylight saving flips. Pull the orders in that window: GET /orders.json?status=any&created_at_min=<start>&created_at_max=<end>&limit=250 (or the GraphQL orders connection with the same created_at filter). Paginate — a flash sale or a creator post can blow past 250 in an hour, and an undercount here would trip a false drop alert. Count the orders (and sum net sales if that's your signal), excluding test:true so QA orders never mask a real problem.
Compare live versus expected, with a floor and a relative threshold
Runs itselfLook up the baseline median for the current hour-of-week and compute the ratio of actual to expected. Two gates decide whether this even counts as a candidate drop: first, the floor — if the expected volume for this hour is below your minimum (say, fewer than ~3 orders normally), skip it entirely, because a dead hour can't meaningfully crash. Second, the threshold — flag it only if the ratio falls under the line you set (for example actual is under 40% of expected). Using a ratio rather than a fixed count is what keeps this honest across seasons and growth: 'half of what this hour usually does' means the same thing in January and during Q4, where a hardcoded 'alert under 5 orders' would scream every slow night and stay silent through a peak-hour outage.
Require the drop to persist before you cry wolf
Needs a humanThe difference between a useful alert and a muted channel is this step. Don't fire on the first sub-threshold check — a real lull and a broken checkout look identical for the first ten minutes. Require the breach to hold for two or three consecutive checks (your 30–45 minute sustained window), tracked in n8n static data or a status row in your sheet: count consecutive sub-threshold runs, reset to zero the moment one comes back normal, and only trip the alert when the count crosses your bar. This single dial is the trade-off between catching a break fast and pinging on every quiet afternoon — tune it once against your own history by replaying a known good week and a known bad day, and leave it.
Add a cooldown and incident state so one outage isn't forty pings
Runs itselfA three-hour outage running on a 15-minute checker is twelve alerts unless you stop it, and twelve alerts is how a channel gets muted right before the one that matters. When the sustained breach trips, write an 'incident open' flag with a timestamp to static data or a sheet, and on every later run check it first: if an incident is already open, stay quiet — don't re-alert. The incident stays open until sales recover (next step) or until a max cooldown you set (say 2 hours) expires, after which a still-broken store is allowed to alert once more as a reminder. The goal is exactly one 'it broke' message per event, not a heartbeat of bad news.
Post the alert to Slack with a triage checklist
Runs itselfAssemble a Block Kit message and post it to a dedicated channel like #store-alerts via an incoming webhook — or use chat.postMessage to @-mention whoever's on call. Lead with the headline: 'Sales down — ~4 orders in the last hour vs ~14 expected for a Monday 1pm (28% of normal).' Then the window and the breach duration so it's clearly not a blip. Then the part that turns an alarm into action: a short checklist of the usual suspects, each a link — test checkout on the live storefront, Meta Ads Manager (paused / rejected / budget-exhausted campaign), your payment gateway's status page, and any theme or app change shipped today. The alert's job is to start the investigation in under a minute, not to diagnose the cause.
Send the all-clear when sales recover
Runs itselfWhen a later check climbs back above your threshold while an incident is open, clear the incident flag and post a short recovery message in the same channel: 'Recovered — ~13 orders in the last hour, back to normal for this time.' This is not a nicety; it's what keeps the alert credible. A channel that only ever shows fires and never resolutions trains people to assume every ping is still burning, or to tune the whole thing out. The open-then-resolved pair also gives you a real incident log over time — how often it trips, how long drops last, and whether your threshold is tuned right — without anyone keeping notes by hand.
02The stack
What it’s built on
- Shopify
- The source of the order flow you're watching. The Admin API serves both the trailing weeks that build the baseline and the rolling hour each live check reads. Shopify Flow has a scheduled trigger now, but it can't hold a 168-hour baseline, compute a median, track a sustained breach across runs, or manage incident state — so this one needs real glue, not Flow alone. An orders/create webhook can feed a lower-latency variant, but the scheduled rolling-window approach is simpler to keep reliable.
- Slack
- Where the alert lands so the right person sees it inside the hour, in the channel the team already watches. A free incoming webhook covers the post; Block Kit gives you the readable card with the actual-versus-expected line and the triage checklist; chat.postMessage adds @-mentions for an on-call person when a drop can't wait for someone to wander past the channel.Incoming Webhooks and the Slack API are free on every plan, including the free tier — there's no upcharge to receive these alerts.
- n8n
- The glue that does the real work: a Schedule node fires the live check every 15 minutes and a daily one rebuilds the baseline, paginated HTTP Request nodes pull the orders, a Code node computes the medians and the actual-versus-expected ratio, and static workflow data holds the consecutive-breach counter, the incident flag, and the cooldown — the state that turns a raw threshold into an alert that doesn't spam.n8n is open-source and self-hostable for free, which covers this end to end; their cloud tier just saves you running the server yourself. The live check is low-volume even at every 15 minutes, so it sits comfortably inside the smallest plan either way.
03Questions
Before you build
How is this different from a daily revenue digest?
Timing, and that's the whole point. A daily digest tells you yesterday was low — useful, but it finds a 2pm checkout break around 7am the next morning, after it's run unattended all night. This watches the trailing hour every 15 minutes and pings while the drop is still happening, so a broken button or a dead ad set gets caught in roughly half an hour instead of half a day. Most stores run both: the digest for the trend, this for the emergency.
Won't this spam me every slow afternoon?
Not if it's built right — almost the entire design exists to prevent exactly that. It compares against the median for this specific hour of the week, so a normally-quiet Sunday morning isn't judged against Friday peak. It ignores 'floor' hours where expected volume is too low to mean anything. It requires the drop to hold for 30–45 minutes before it says a word. And once it alerts, a cooldown keeps it from re-pinging until sales recover. A naive 'alert when orders < 5' would cry wolf nightly; this is engineered not to.
How does it know what 'normal' is for right now?
It builds a baseline from your own trailing history — the last 4 weeks of orders, bucketed into the 168 hours of the week, using the median for each so one viral day doesn't skew the bar. So 'expected for Tuesday 2pm' is literally what your store has actually done at Tuesday 2pm recently. It rebuilds daily, which means it tracks real growth instead of flagging a bigger store as a constant drop. The honest catch: a brand-new store without a few weeks of history has a weak baseline, so it's worth waiting for real data or starting with a simpler 'no orders in X hours' heartbeat.
Will it work for a small store with only a few orders a day?
It works, but it needs tuning, and honesty matters here. At very low volume, normal randomness looks like a drop — going from two orders an hour to zero is half the time just luck, not a broken checkout. The relative threshold and the floor handle a lot of it by simply not alerting on hours that are normally near-empty. But below roughly a steady handful of orders an hour, a 'longest gap between orders' heartbeat (alert if no order in 3–4× your typical gap) is often a cleaner fit than a percentage-of-baseline check. The teardown is where we'd tell you which one your volume actually warrants.
Can it tell me why sales dropped?
No, and it shouldn't pretend to. It detects that orders fell off against normal; it can't know whether checkout broke, an ad got paused, a gateway is down, or a theme change shipped a bug. What it does is hand you a fast head start: the alert includes a triage checklist linking to the usual culprits so someone can rule them out in minutes. Trying to auto-diagnose the cause is where these systems get unreliable and start lying to you — far better to be a trustworthy smoke alarm than a guessing one.
Can I build this myself, or should you build it?
Every piece is written out above on purpose — the baseline, the windowing, the floor, the relative threshold, the sustained-breach counter, the cooldown and recovery — so if you've got the API comfort and a few hours, build it and own it outright. If you'd rather not own the median math and the incident state, we'll build it on the Shopify, Slack, and n8n accounts you already pay for, tune the thresholds against your real history so it catches breaks without crying wolf, document every node, and hand you the keys. You own the workflow — no monthly retainer, and nothing stops working if you walk away. It starts with a free 20-minute teardown of where your store actually goes quiet, not a contract.
Related recipes
Reporting & dashboards
Send a daily revenue digest to Slack
Every morning starts the same way: open Shopify, wait for the dashboard to load, read yesterday's number, close the tab. This does it for you. Once a day it pulls yesterday's orders from Shopify, totals net sales, order count, AOV, and units, compares it to the same weekday last week so the number actually means something, and posts a clean digest to a Slack channel before you've finished your coffee. Build it yourself with the steps below, or we build it on your own accounts, wire the math and the comparison, and hand you the keys so you own it with no monthly fee. Free teardown of what you check by hand every morning first.
- Shopify
- Slack
Reporting & dashboards
Automate a daily ad spend vs revenue report
To know if yesterday's ad spend paid off, someone opens Ads Manager for the spend, opens Shopify for the revenue, and does the division in their head — across two timezones and two definitions of a 'sale.' This does it for you. Once a day it pulls yesterday's spend from Meta, yesterday's revenue from Shopify, lines the two day-windows up so they're actually comparable, computes blended ROAS (and shows Meta's own attributed ROAS next to it so the gap is visible), and posts one clean line to Slack. Build it yourself from the steps below, or we build it on your own accounts, reconcile the windows and the currency, and hand you the keys so you own it with no monthly fee. Free teardown of how you check whether ads are working first.
- Meta Ads
- Shopify
- Slack
Reporting & dashboards
Build a real-time sales dashboard in Google Sheets
Every week you export a Shopify CSV, paste it into the same spreadsheet, fix the columns, and rebuild the same pivot — to land on a number you could have had automatically. This wires it up instead: each paid order appends itself to a raw log tab in Google Sheets within a minute or two, a scheduled reconcile job catches anything the webhook missed, and a dashboard tab built on QUERY formulas recalculates the moment a row lands. Build it yourself with the steps below, or we build it on your own Shopify and Google accounts, reconcile it against your Shopify Analytics, and hand you the keys so you own it with no monthly fee. Free teardown of the report you rebuild by hand first.
- Shopify
- Google Sheets
- n8n
Inventory & suppliers
Send low-stock Slack alerts from Shopify inventory
A bestseller running down to zero with nobody watching is the most avoidable stockout there is. Here's how to watch Shopify's live inventory and drop a clear, actionable alert in Slack the moment a SKU crosses its low-stock line — once per dip, not on every sale.
- Shopify
- Slack