On this page
We already had a breaker running in production. It watched our own ad set spend $70.15 for zero leads and never fired. The rule was not wrong; it was written for the wrong level. Episode 06 is the workflow we built to close that gap, plus the honest record of the run that found it.
A Meta campaign can be paused while a child ad set underneath it stays
configured ACTIVE. Pausing the campaign does not touch the ad
set's own configured_status, so an ad set can keep accruing spend
long after its own flight window has ended, invisible to anyone only watching
the campaign. Our own live breaker is a campaign-level rule and cannot see
that gap by construction. This workflow watches the ad set instead.
If iframes are blocked where you are reading this, the video is at youtu.be/jrVFBmUCsfo.
act_••••3502,
••••0014). This is our own lead-generation ad set for HVAC
services, run and paid for by SkynetLabs. No client account was read, logged
or touched anywhere in this build.
What does the circuit breaker actually do?
A poller reads one ad set's insights from the Meta Marketing API on a fixed schedule, and every field is masked before anything is written to storage. The masked row is checked against three caps that live in an n8n Data Table, not buried in a Code node, so a threshold can change without touching the workflow's logic. A deterministic rules engine reads those caps in a fixed order and decides one of three outcomes: nothing happened, the breaker has tripped on repeated errors, or the ad set should pause. A pause verdict still has to pass a whitelist gate that builds the write itself, from fixed constants, before it can reach Meta. PAUSE IS THE ONLY WRITE IT CAN MAKE. No code path here can raise a budget, change a bid, or touch targeting. Every decision is written to Postgres as a receipt: the rule that fired, the numbers behind it, and whether the write was real or a dry run.
What did the honest run actually show?
On 2026-09-23 at 09:34 local time (2026-09-23T01:34:47Z UTC), one sanctioned manual execution read the ad set's insights over the window 2026-08-30 to 2026-09-22, 24 days, and evaluated them against the caps row. The caps were breached, and the breaker decided to pause. No write reached Meta: the default rule is that silence on a pause-permission question means dry run, and no permission had been given.
| Metric | Observed |
|---|---|
| Spend | $70.15 |
| Leads | 0 |
| Clicks | 44 |
| Impressions | 982 |
| CTR | 4.48% |
| Spend cap | $60.00 |
| Over the cap by | $10.15 |
| Rule fired | R1, spend cap |
| Write sent | None. Dry run. |
Read the zero leads honestly. The breaker caps spend, not the funnel: 44 clicks and zero bookings can also mean the landing page or the offer needs work. The workflow makes no claim about that. Its claim is narrower and verifiable from Postgres: the ad set crossed a spend threshold with nothing to show for it, and the rule built to catch that condition caught it.
Why did the live production breaker miss it?
Our existing breaker is older and lives one level up, on the campaign. Its rule needs at least 40 clicks on the campaign before it fires. The campaign this ad set sits under had only 32 over the same window. The ad set itself had 44, four more than the threshold, sitting unseen because nothing was reading at that level. Two click counters, two different numbers: not a bad threshold, a rule scoped one level too high.
The cap sheet
The three caps, the breaker, and the error workflow
Three deterministic rules sit ahead of the pause verdict, checked in a fixed order: a cost-per-lead breach, a spend-cap breach, and a zero-lead-spend breach. When two caps are set to the same number, as ours were here, the earlier rule fires and the later one is never evaluated for that row. That is the documented order, not a bug, and it is exactly why this run's receipt reads R1, spend cap, rather than the zero-lead rule that was also true. A separate error workflow counts consecutive API failures and trips the breaker on its own, independent of any spend rule, so a broken connection to Meta cannot silently do nothing forever.
This is a loop, and it is treated like one: no automation loop goes live without a step cap, a wall-clock cap, and a spend cap, backed by a circuit breaker. SCHEDULE TRIGGER, DISABLED FOR THE RECORDING. It sits on the canvas as the swap point for a live 15-minute cadence; the one execution behind these numbers was a single manual test, and the schedule stays off until a human turns it on.
Privacy, by construction
Ids are masked before anything is stored, logged, or rendered on screen, not after. Account and ad-set references only ever reach Postgres, the receipt, or the video in their masked form. No client data passes through this workflow at any point; it only ever points at our own ad set.
How do I import it and where are the swap points?
The free workflow JSON is MIT-licensed at github.com/waseemnasir2k26/n8n-workflows/tree/main/workflows/06-meta-ads-breaker. Import it, run the schema, create a Data Table for the caps row, and point it at one ad set of your own.
Three swap points are built into the canvas on purpose:
- A Slack node that posts the receipt for a human, shipped disabled.
- A sample-insights lane that replays a fixture file instead of calling the live Meta API, for testing the rules without spending a real API call.
-
A
dry_runflag on the caps row, defaulted on, so the workflow can decide and write a receipt without ever sending the pause. Flip it only once you know exactly which ad set the id in that row points at.
One gotcha for your own caps row: if the spend cap and the zero-lead-spend cap are set to the same number, the spend-cap rule fires first and the zero-lead rule is never evaluated, even when both are true. Set them apart if you want the receipt to name the zero-lead condition specifically.
Frequently asked questions
Why did an existing ad-spend breaker miss a real overspend?
Because the rule lived at the campaign level and needed 40 clicks to fire. The campaign under this ad set only saw 32 clicks over the window, while the ad set itself had 44. The loss was visible at the ad-set level and invisible at the campaign level, so no threshold on the campaign rule could have caught it.
What is the only write this workflow can make?
A single status change on one ad set, built from fixed constants inside a whitelist gate rather than caller-supplied input. It cannot raise a budget, change bidding, or touch targeting. Pause is the only write it can make, and every attempt is written to Postgres as a receipt.
Was this run a real pause, or a simulation?
It was a dry run. The breaker read real insights, evaluated them against real caps, and decided to pause: spend $70.15 against a $60.00 cap, ten dollars and fifteen cents over. No write was sent; the ad set's configured status was unchanged before and after.
Does this workflow ever touch client data?
No. It only points at our own ad set, and every account, campaign and ad-set id is masked before it is stored, logged, or shown on screen. Nothing here is wired to a client's Meta account.
The rest of the series
One workflow per video, the JSON given away under MIT each time, built by Waseem Nasir at SkynetLabs.
- Episode 05, n8n clinic WhatsApp booking agent: a code guard hands off medical and price questions before the model.
- Episode 04, n8n freight quote parser: one quote email in, eight validated fields out.
- Episode 03, n8n Maps lead harvest: a trade and a city in, a deduped lead table out.
Want a breaker scoped correctly for your own ad accounts? Book a discovery call with SkynetLabs.
WhatsApp +92 300 1001957 · Waseem Nasir, SkynetLabs
Hire SkynetLabs, our Top Rated agency on Fiverr: https://www.fiverr.com/agencies/skynetjoellc