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.


💡 In one sentence: a resumed session looks identical to a finished run, so completion tags need a gate that only lets them fire on a page where the story actually started.


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

progress = 0, depth = 1

Story start, then halfway, then complete — in order, as the person moves

Resumed, mid-story

progress = 47, depth = 5

Anything whose threshold is already passed

Resumed, finished

progress = 100, depth = last

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.


⚠️ A real example. A test pixel showed 8 story completions against 4 story starts, with 5 qualified leads. Nobody had completed the story 8 times. Roughly half of those conversions were page reloads of an already-finished session.


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 page equals false


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 progress = 0, so it is safe by construction

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.

  1. Open the story in a private window and play it through to the end. Milestone tags should fire normally.
  2. Reload that same finished page. In Preview, the gated tags should now appear under Tags not fired.
  3. Reload twice more. Still nothing.


⚠️ Testing a story you have already finished will look broken. The story resumes at the end, the gate suppresses everything, and you see no events. That is the gate working, not a failure. Always start from a private window when you want to see a full funnel.


✅ 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

Was this article helpful?

Share your feedback

Cancel

Thank you!