Meta - Why completions get over-counted (and how to fix it)
Why completions get over-counted (and how to fix it)
Story sessions are saved. When someone reopens a story link, the player restores where they left off — and if they had finished, it reports the story as complete again, immediately, before they touch anything.
Every milestone tag fires on that. So a returning visitor who simply reloads the page books another completion, and another qualified lead, every single time.
This affects Analytics and Meta equally, and it has probably been happening in every story you have ever measured.
What the player actually pushes
The difference is visible in the very first event of the page.
Situation | First progress event on the page | What fires |
|---|---|---|
Genuine run |
| Story start, then halfway, then complete — in order, as the person moves |
Resumed, mid-story |
| Anything whose threshold is already passed |
Resumed, finished |
| Halfway and complete and any score-based event, all within two seconds of page load |
A genuine run always opens at progress = 0. A resumed session never does. That single fact is the whole fix.
How to spot it in your data
The symptom is a funnel that widens at the bottom. Completions exceeding starts is impossible in reality, so if you see it, you are counting reloads.
Check this before you trust any story funnel:
- Completions should never exceed starts.
- Halfway should never exceed starts.
- If a score-based or qualification event is close to your completion count, be suspicious — both inflate together.
The fix in Tag Manager
Three pieces: a variable that answers "did this page see a genuine start", a trigger that fires when the answer is no, and that trigger attached as an exception on the tags that need protecting.
1. The variable
Variables → New → Custom JavaScript. Name it JS - story started this page.
function() {
try {
var dl = window.dataLayer || [];
for (var i = 0; i < dl.length; i++) {
var e = dl[i];
if (e && e.event === 'intractiveStoryProgress' && Number(e.progress) === 0) {
return 'true';
}
}
} catch (err) {}
return 'false';
}
It scans everything the page has pushed so far and looks for a genuine start. Tag Manager re-evaluates it every time a tag is considered, so it is correct at the moment it matters.
2. The blocking trigger
Triggers → New → Custom Event. Name it Block - resumed session (no story start).
- Event name:
.*with use regex matching ticked - Fire on: Some Custom Events
- Condition:
JS - story started this pageequalsfalse
3. Attach it as an exception
Open each milestone tag → Triggering → Add Exception → choose the blocking trigger.
Gate these | Why |
|---|---|
Leave these alone | Why |
---- | ---- |
Halfway / 50% events | Re-fires on any resumed session past the midpoint |
Story complete events | Re-fires on every reload of a finished story |
Score or qualification events | Restored variables re-trigger the threshold |
Answer / variable-updated events | Restored answers are not new answers |
Story start / ViewContent | Only fires at |
Outbound click / Lead | A click is a real action, whenever it happens |
Block view | Someone resuming genuinely is viewing that block |
Config and base pixel tags | Page-level, not milestones |
The same fix without Tag Manager
If you are running the pixel from Footer scripts instead, the gate is one flag rather than three objects:
if (Number(o.progress) === 0) { started = true; }
if (!started) return; // resumed session — send nothing
This is already built into the standard snippet. See the article on adding a Meta pixel without Tag Manager.
Verifying it works
You need a finished session to test against, so do it in this order.
- Open the story in a private window and play it through to the end. Milestone tags should fire normally.
- Reload that same finished page. In Preview, the gated tags should now appear under Tags not fired.
- Reload twice more. Still nothing.
✅ Quick checklist
- A genuine run starts at
progress = 0; a resumed one never does. - Gate halfway, complete, qualification and answer events.
- Do not gate start, outbound click, or block view.
- Sanity check forever after: completions must never exceed starts.
- Test from a private window, not from a story you already finished.
Updated on: 24/09/2026
Thank you!