The most dangerous billing alert isn’t the one that fails to fire. It’s the one that fires so often that people stop reading it. A channel full of “spend is up 12%” messages trains everyone to scroll past, and the day a retry loop starts writing a billion rows, its alert looks exactly like the last forty.

Alert noise is a design problem, not a discipline problem. You won’t fix it by asking people to pay more attention. You fix it by making every alert rare enough and specific enough that it’s worth attention when it shows up.

Here’s how we think about it, and a checklist at the end.

Every alert needs a job

Before you create an alert, finish this sentence: “When this fires, someone will ___.”

If the answer is “look at it and go back to work”, it’s an FYI. That’s allowed, but it belongs somewhere quiet. If the answer is “stop what they’re doing”, it’s urgent and it should be loud. If you can’t finish the sentence at all, don’t create the alert.

That one rule gets rid of most noise. The rest of this post is about getting the details right.

Thresholds that mean something

Pick thresholds as a fraction of a budget you actually believe in, not round numbers you picked once. A budget set at twice your normal monthly spend means 50% is “normal month, on track”, which is useless information. A budget set at your real expected spend plus some headroom makes each threshold mean something.

We default to 50%, 80% and 100% of each budget:

  • 50% is early-month context. If it fires on the 6th, that’s interesting. If it fires on the 20th, it’s expected. Either way it’s FYI.
  • 80% is the “check it today” line. Something might need a look before the month ends.
  • 100% is the line. Somebody acts.

Three is enough. Five thresholds per budget is five chances to train people to ignore you.

And remember that in SignalFlare, as in any tool built on Cloudflare’s analytics, spend is an estimate. The analytics are sampled and lag a few minutes. That’s fine for this job, because the decision “go look at D1 now” doesn’t need cent-level accuracy, but it does mean you shouldn’t set thresholds so tight that ordinary drift trips them.

Forecast versus threshold

A threshold alert tells you where you are. A forecast tells you where you’re heading. They answer different questions, and you want both:

  • Threshold: “You’ve used 80% of the R2 budget.” True, verifiable, but late if usage is accelerating.
  • Forecast: “At this rate, you’ll cross the R2 budget on the 24th.” Earlier, but a projection can be wrong, especially early in the month when there’s little data.

The trap is forecast alerts that re-fire every time the projection wiggles. A forecast that says “you’ll go over” on Monday, “you won’t” on Tuesday and “you will” again on Wednesday is three alerts about one fact.

In SignalFlare, a projection alert fires once per budget per month, the first time the trend projection crosses 100% while actual spend is still under it. After that, the threshold alerts take over. One early warning, then the facts.

Cloudflare’s own budget alerts work on cumulative spend for the billing period and are processed daily, so they’re threshold alerts with a day’s delay. That’s another reason to keep them on as a backstop rather than your only signal.

Dedupe: one alert per threshold per month

If your alerting checks spend every few minutes and you’re over 80%, a naive rule fires every few minutes. Nobody needs to hear that 288 times a day.

The rule we use, and recommend for anything you build yourself:

  • Each threshold fires at most once per budget per month. Crossing 80% on the 14th is one message, not one per poll.
  • Only the highest crossed threshold fires. If a spike carries spend from 40% to 110% in one poll, you get the 100% alert, not 50, 80 and 100 in a burst. And once 100% has fired, a late 50% message never trails in after it.
  • Spike alerts dedupe per account per day. A spike that lasts six hours is one alert, not seventy.
  • Operational alerts dedupe per day too. “Your API token stopped working” should arrive once a day until it’s fixed, not every poll.

If you’re writing your own alerting, the easiest way to get this right is a unique key in the database: (budget, kind, threshold, period). Insert the alert event with “on conflict do nothing”, and only send a notification when the insert actually created a row. That makes dedupe a property of the data, not of code you hope runs correctly.

Per-service budgets

An account-wide budget is a smoke detector for the whole building. It goes off eventually, but it doesn’t tell you which room.

The worse problem is that one service running away can hide inside the total. If your Workers bill is steady and large, a D1 loop doubling a small D1 line barely moves the account number, so the account-wide alert stays quiet until the damage is big.

Give the services that can run away their own budgets: D1 rows written, R2 operations, Workers requests, Durable Objects. Then the alert text says “D1 is at 100%” instead of “the account is at 80%”, and the person reading it already knows where to look.

You don’t need a budget for every service. Put them on the ones with usage you don’t fully control: anything a crawler, a user, a retry or a cron can multiply.

Route by severity

Not every alert should reach every person in the same way. The pattern that works:

SeverityExamplesWhere it goes
FYI50%, early forecastA spend channel, read whenever
Check today80%, forecast with little time leftThe team channel
Act now100%, spike, cap firedA pager, to whoever is on call

In SignalFlare, channels are chosen per budget. So the practical setup is two budgets on the same thing: a “soft” budget at your expected spend that posts to Slack or Discord, and a “hard” budget higher up that posts to a signed webhook which opens a PagerDuty incident. The webhook body includes the alert kind and threshold, so a small Worker in between can also split by severity if you’d rather use one budget. Our post on when billing alerts aren’t enough has a full example of that Worker.

Quiet versus loud

Quiet and loud are different design choices, not different volumes of the same thing.

Quiet alerts are for context. They go to a channel nobody is obliged to watch. They should be short: one line that says what crossed and how far, with a link. People skim them in the morning.

Loud alerts are interruptions. They should be rare: if your pager goes off for billing more than once or twice a month, your thresholds are wrong. They should say exactly what’s happening, which service, how fast, and what the first step is.

If an alert can’t decide which one it is, it’s both too loud to ignore and too frequent to take seriously. That’s the one that ends up muted.

Test alerts

An alert route you’ve never tested is a guess. Webhook URLs get rotated, Slack apps get uninstalled, someone leaves and their email was the only recipient.

  • Send a test to every channel when you set it up. In SignalFlare, each channel has a Send test button.
  • Test the loud path end to end: the test should actually open a PagerDuty incident and someone should actually get paged.
  • Re-test after anything changes: new on-call rotation, new Slack workspace, rotated webhook secret.

For Cloudflare’s own budget alerts, the simplest test is to make sure the recipient list includes an address that routes somewhere visible, and that you’ve seen at least one email from Cloudflare arrive there.

Attach a runbook

The worst moment to work out what to do is when the pager has just gone off. Write the runbook in advance, and put the link where the alert lands: the channel topic, the PagerDuty service description, the top of your on-call doc.

A billing runbook can be short. Ours looks roughly like this:

  1. Which budget fired, and which service is driving it? Open the dashboard and look at the per-service breakdown.
  2. Is it real? Compare against your own app metrics. Was there a launch, a migration, a backfill?
  3. Is it still happening? Look at the last hour, not the month to date.
  4. If it’s a loop or abuse: apply the cheapest fix that stops it (see below).
  5. If you capped something: confirm what changed, tell the team, and plan the restore.
  6. Afterwards: write down what happened and whether the alert fired early enough.

What to do when an alert fires

When it’s a real runaway, the order matters. Start with the least disruptive fix that stops the bleeding, and escalate only if it doesn’t.

  1. Find the source. Which Worker, which route, which bucket, which cron.
  2. Degrade first if your app supports it. If your code reads a budget status API, the expensive features may already be off.
  3. Block the bad traffic if it’s coming from outside: a WAF rule or a rate limit on the expensive path.
  4. Cut the billing source if it’s internal: pause the cron, pause the queue, take the route down.
  5. Restore once it’s fixed, and watch the next few polls to make sure spend has flattened.

Our mitigation recipes cover each Cloudflare-native way to stop runaway traffic, with what it breaks and how to undo it.

If you’d rather not be the person who does step 4 at 3am: SignalFlare can do some of it for you when a budget hits 100%. It can remove a Worker route, enable a pre-built WAF rule that blocks or rate-limits, pause cron triggers, or turn off a workers.dev URL. Each one starts as a dry run, needs a typed confirmation and an owner or admin to arm it, is logged, and can be restored in one click. It works on estimates polled every 5 minutes on paid plans, so it trips minutes after the line, not at the exact cent.

The checklist

  • Every alert finishes the sentence “When this fires, someone will ___.”
  • Budgets are set at realistic expected spend, not round numbers.
  • Three thresholds per budget at most (50/80/100 is a good start).
  • Forecast alerts are on, and fire once per month, not on every wiggle.
  • Each threshold fires once per budget per month; only the highest crossed one sends.
  • Services that can run away have their own budgets.
  • FYIs go to a channel; act-now alerts go to a pager.
  • Loud alerts are rare: about once or twice a month at most.
  • Every channel has had a test sent, and the pager path has been tested end to end.
  • The runbook link is where the alert lands.
  • Cloudflare’s own budget alerts are on as a free backstop.

SignalFlare sends billing alerts for Cloudflare that fire once, say which service, and go to the right channel. Every number is an estimate, an early warning and not your invoice. Start a 14-day trial.