Instagram's 'Seen' Is a Lie — And They're About to Charge You for the Proof

Valentin Lobstein / / Updated April 18, 2026
Table of Contents

TL;DR

When you view an Instagram story, two things happen: the content downloads, and a separate call tells Instagram you saw it. Block the second call and you’re invisible. One line of code. This has been possible since at least 2019. Now Meta is testing a $2/month subscription to sell this exact capability. I built a browser extension that does it for free, plus a bunch of things their subscription doesn’t even offer.

Source code: instagram-story-research


How it started

I was scrolling Instagram on my phone and noticed something odd: someone had deleted their story, but I could still see it in the app. The cache hadn’t cleared yet. Pure curiosity kicked in. As a security researcher, when something behaves unexpectedly, I dig. I wanted to understand how story delivery works under the hood.

I opened DevTools on the web version, clicked on a story, and looked at the network tab. I expected something complicated. What I found was embarrassing.


Two calls

When you view an Instagram story, the browser makes two separate GraphQL calls:

1. PolarisStoriesV3ReelPageGalleryQuery  →  downloads the story
2. PolarisStoriesV3SeenMutation          →  tells Instagram you viewed it

The content is already in your browser before Instagram knows you saw it. The “seen” indicator - the one that shows your name in the viewer list, the one that 2 billion people trust - is a separate HTTP request that your browser sends AFTER you’ve already seen the content.

Normal flow:

sequenceDiagram
    participant Browser
    participant Instagram

    Browser->>Instagram: GalleryQuery (fetch story)
    Instagram-->>Browser: Story content
    Note over Browser: Content rendered
    Browser->>Instagram: SeenMutation
    Note over Instagram: You appear in the viewer list

With the extension:

sequenceDiagram
    participant Browser
    participant Extension
    participant Instagram

    Note over Extension: Extracts doc_ids from JS bundles
    Extension->>Instagram: Auto GalleryQuery (no click needed)
    Instagram-->>Extension: Story content (intercepted via StreamFilter)
    Extension-->>Browser: Content passed through
    Note over Extension: Saved to disk + metadata cached + CSV exported
    Browser-xInstagram: SeenMutation blocked (cancel: true)
    Note over Instagram: You were never here

Block it and you’re invisible:

if (body.includes("PolarisStoriesV3SeenMutation")) {
  return { cancel: true };
}

For non-developers: when you use Instagram, your browser (the “client”) and Instagram’s servers communicate using HTTP. As MDN puts it: “When you click a link on a web page, submit a form, or run a search, the browser sends an HTTP Request to the server.” The server processes it and sends back a response. That’s all the web is: requests and responses between your browser and a server.

When you view a story, your browser sends a request to fetch the content (the server responds with the story), and then sends a second request to tell Instagram you saw it. This code checks if your browser is about to send that second “I saw this story” request. If it is, it tells your browser to not send it. That’s it. And to be clear: the cancel: true is an instruction to your own browser, the client. It’s not a message to Instagram’s servers. Nothing is sent. No packet leaves your machine. From Instagram’s perspective, the request never happened. There’s nothing to detect, nothing to log, nothing to block. The request simply doesn’t exist.

A standard browser API that Firefox and Chrome have supported for over a decade. No hack, no exploit, no reverse engineering. Just… not making a call.


But now Meta wants to charge for it

Instagram is testing Instagram Plus, a paid subscription currently in Mexico, Japan, and Philippines. The headline feature? Anonymous story viewing. Pay $1-2/month and your name won’t appear in the viewer list.

Meta projects $2.4 billion in annual revenue if 1% of users subscribe.

The full Instagram Plus package includes anonymous viewing, searchable viewer lists, extended story duration, animated Superlikes, and engagement metrics. But nobody pays $2/month for animated Superlikes. They pay to view without being seen and to know who’s viewing them.

Both of those features have always been free in the architecture.

The product is a cancel: true on a GraphQL call. That’s the $2.4 billion business.


This isn’t new

I thought I’d discovered something. I hadn’t. Browser extensions doing this have existed since 2019. Seven years ago. Zero stars, zero attention, zero media coverage. The capability has been public for seven years and nobody outside of a handful of developers noticed.

And it’s been doable for free this entire time. Now Meta packages it as innovation and the tech press covers it like it’s new.


So I built the full thing

I wrote a Firefox extension as a proof of concept. It does more than Instagram Plus for $0.

Invisible viewing. Blocks the SeenMutation at the network level. You see stories normally. Your name never appears.

Automatic fetching. The extension extracts story tray user IDs from Instagram’s SSR HTML, dynamically captures GraphQL doc_ids from JS bundles, and fires its own GalleryQuery. Stories are cached automatically when you load instagram.com. No clicking.

Local download. Every story is saved to ig_stories/<username>/<date>_<id>.jpg (or .mp4). If someone deletes a story 5 minutes later, the file is already on your disk. To be clear: your browser already caches this content locally when Instagram serves it to you. That’s how the web works. When you view a story, the image or video is downloaded to your machine so your browser can display it. It sits in a temporary cache folder that gets silently wiped later. The extension doesn’t access anything new. It takes the same content your browser already has and saves it to a named file. The data was already on your machine. No additional request, no additional access. This just makes it persistent and organized instead of hidden in a temp folder you’ll never look at. The point isn’t to hoard other people’s content. The point is to prove that nothing stops you, and that Instagram could do more to protect it.

Deletion detection. If a story disappears before its 24h expiry, it’s flagged as deleted. The file persists.

Asymmetric visibility. This is the worst part. The extension blocks your seen receipts to others, but your own story metadata includes the full viewer list with user IDs. You see who views your stories while being invisible when viewing theirs. Instagram Plus sells anonymous viewing. This extension gives you anonymous viewing AND full viewer analytics. For free.

CSV history. Every cache update exports a timestamped CSV with username, media ID, music, caption, audience, viewer count (your own stories only), file path, deletion status.

Auto-refresh. Replays the GalleryQuery every 5 minutes with pagination.


The real question

The SeenMutation has been a separate call since stories launched. Meta’s engineering team designed it this way. There are two explanations:

It’s an oversight. Meta never noticed that the seen indicator has zero server-side enforcement. For years. On a platform with 2 billion users. With thousands of engineers. Unlikely.

It’s intentional. They always planned to monetize the toggle. The separation between fetch and seen was a business decision. The “seen” was never a privacy guarantee. It was a future revenue stream parked in the architecture, waiting for the right moment.

To be fair: I can’t prove intent. What I can prove is capability. Instagram Plus also includes a rewatch count, showing how many times someone re-viewed your story. That feature is server-side. It has to be, the client can’t track other people’s repeat views. So Meta knows how to do server-side enforcement. The rewatch count proves it. They just chose not to do it for the seen. Whether that choice was made in 2016 with monetization in mind or in 2026 when someone realized they could sell it doesn’t change the outcome. The capability existed. The fix was never shipped. The subscription was.

They could fix it. Serve stories on-demand instead of pre-fetching them, record the seen server-side when the content is served. One round-trip. Done. Or encrypt the pre-fetched content and gate the decryption key behind a server-side seen confirmation. Both are standard patterns. Both work. They just cost ~200ms of latency per story view.

And that’s the real answer. Instagram pre-fetches stories so they load instantly when you tap the tray. No spinner, no delay, just content. That instant loading is what keeps people swiping. Swiping is engagement. Engagement is time on the platform. Time on the platform is ads viewed. Ads viewed is revenue. The pre-fetch that makes stories feel instant is the exact same pre-fetch that makes the seen unenforceable. Fix the seen, lose the instant load. Lose the instant load, lose engagement. Lose engagement, lose ad money. They chose a broken privacy indicator over 200ms of latency because 200ms of latency costs more than your privacy.

And now they’ve found how to monetize both sides. The pre-fetch generates engagement (ad revenue). The broken seen becomes Instagram Plus (subscription revenue). They profit from the flaw and they profit from selling you the fix for the flaw. Same architecture, two revenue streams. Your privacy was never the product. Your privacy was the business model.

In both cases: every user who relied on the “seen” indicator was trusting a system with zero enforcement. It works only if every viewer uses the unmodified official client. One browser extension and it’s gone.


A GDPR question

Instagram operates in the EU under GDPR.

Article 25(2) states that controllers must implement measures ensuring that “by default personal data are not made accessible without the individual’s intervention to an indefinite number of natural persons.” The “seen” indicator is exactly that mechanism: it lets users know who accessed their story. But it has zero server-side enforcement. Anyone with a browser extension can access stories without the poster ever knowing.

Article 32(1)(d) requires “a process for regularly testing, assessing and evaluating the effectiveness of technical and organisational measures for ensuring the security of the processing.” If Meta had tested the effectiveness of the “seen” indicator at any point in the last seven years, they would have found that it can be bypassed with one line of JavaScript. Either they never tested it, or they tested it and chose to monetize the bypass instead of fixing it.

Even Proton, a Swiss privacy company, called it out publicly: “For stalkers, disgruntled exes, harassers, or obsessive viewers… this feature removes that friction” and “Meta seems willing to make posters’ loss of privacy a paid feature for viewers.” Meanwhile, Instagram is also dropping end-to-end encryption on DMs. Remove DM privacy, sell story privacy. Both at the same time.

I’m not a lawyer. But this feels like a question that the EDPB and national data protection authorities should be asking.


Why Mexico, Japan, and the Philippines

Instagram Plus is being tested in Mexico, Japan, and the Philippines. Not Germany. Not France. Not Ireland, where Meta’s EU headquarters are and where the DPC has jurisdiction.

That’s not a random product rollout. That’s a legal strategy.

If Meta launched anonymous story viewing in the EU first, it would be an implicit admission: “the seen indicator was never enforced, and now we’re selling the bypass.” Any EU data protection authority could use that as grounds for a GDPR complaint under Articles 25 and 32. Meta would be admitting the flaw exists and monetizing it instead of fixing it.

By testing in countries with weaker privacy regulations, Meta avoids setting that legal precedent. If it works commercially in those markets, they can refine the product, build the revenue case, and figure out how to frame it for EU regulators later.

The question is: does testing it outside the EU change the fact that the flaw exists inside the EU right now? Every EU user’s “seen” indicator is just as broken as every Mexican user’s. The only difference is that in Mexico, Meta is honest about it (for $2/month). In the EU, they pretend it works.


The ghost that likes

Here’s a fun one. With the extension active, you can like someone’s story without appearing in their viewer list. They get the notification “X liked your story” but when they check who viewed it, your name isn’t there. You’re a ghost that leaves likes.

The like and the seen are two completely independent systems. The extension blocks the SeenMutation but not the like call. Instagram never bothered to link the two. You can interact with a story without the platform registering that you viewed it.


A note on intelligence use

The asymmetric visibility isn’t just a privacy concern. It’s an intelligence tool. Anyone doing surveillance on Instagram gets: invisible story viewing, full viewer lists on their own decoy stories, media downloads of deleted content, and timestamped logs. All from a browser extension. No zero-day. No exploit. Just the API working as designed.


Beyond stories: DMs, presence, and the same problem everywhere

The same architectural pattern likely applies across Instagram. On the web client, Instagram doesn’t even offer the “disable read receipts” toggle that the mobile app has. The content loads first, the “seen” is reported separately, just like stories.

On mobile, the read receipt toggle is mutual: turn it off and you lose the ability to see when others have read your messages. The architecture I documented on stories breaks that symmetry. Block the receipt on the web, keep seeing theirs. Same asymmetric visibility.

This isn’t a secret either. People have been doing the airplane mode trick for years: turn off your connection, open the story or DM, read it, force-close before reconnecting. The content was already cached locally, the “seen” never fires. The extension just automates what everyone already does manually.

The same logic likely applies to the “online” status. Your presence is a heartbeat your client sends. Block it and you’re invisible, but you still receive everyone else’s. Same asymmetry, same architecture. And this one is harder to fix: Instagram already offers an official “hide activity status” toggle, meaning they’ve already separated the data connection from the presence signal. They can’t fuse them back without breaking their own feature.

Instagram could fix all of this with server-side confirmation round-trips. Don’t serve the content until the receipt is acknowledged. But that would break the caching model that makes stories and messages feel instant. Performance over privacy enforcement. That’s the trade-off they made.

I haven’t tested DMs or presence. These are hypotheses based on the same architectural pattern I confirmed on stories. If someone wants to dig into it, the endpoint names are probably just as discoverable.


Why I’m publishing this

I’m an Instagram user, but I wasn’t following tech news when Instagram Plus was announced. My friends told me about it. They were the ones panicking. People who check the viewer list every day to see if an ex is still watching. People who use the “seen” as a safety signal to know if someone they blocked on their main is stalking them from another account. People who felt safe because they could see who was looking, and who suddenly realized that safety was about to be sold for $2/month.

These aren’t hypothetical users. These are people I know. People I talk to every day. The fear was real. I don’t usually have my head in this kind of thing, but between what they told me and something I’d noticed earlier, something clicked. I’d seen a deleted story still visible in my app. The cache hadn’t cleared. And I thought: if I can still see a story that’s been deleted, did the poster ever know I saw it? If the content outlives the deletion, what else is the client handling on its own? Was the “seen” ever real to begin with? I just wanted to see what was under the hood. It wasn’t. The “seen” was never enforced. It was never secure. It was just a call that the official client happened to make. And it’s been bypassable for free since 2019.

I built the extension to see how far it goes. The prior art was broken (old REST endpoints that Instagram migrated away from). I wanted a working proof of concept with the real GraphQL endpoints, the real metadata, the real downloads. To document how deep the flaw goes.

A privacy feature that 2 billion people trust was never real. And now Meta wants to sell the proof.


What you can do about it

Nothing, really. If you post stories, anyone with a browser extension can view them without appearing in your viewer list. This has been true since 2019. There is no setting you can toggle, no privacy option you can enable, no way to force viewers to send the seen receipt. The “seen” list was never a complete list. It was a list of people who didn’t block the call.

If the viewer list matters to you for safety reasons, don’t rely on it. It was never reliable. If someone isn’t in your viewer list, it doesn’t mean they didn’t see your story. It means their client chose to tell you. That’s all it ever meant.

Instagram does offer tools for this. The “hide story from” feature blocks specific people from seeing your stories, and the “restrict” feature hides your read receipts from someone. Both are server-side. Both actually work. No extension can bypass them. But they only work if you know who to target. If someone is viewing your stories anonymously, you don’t know they’re there, so you can’t hide from them. And the restrict feature is worth noting: it proves Instagram knows how to hide read receipts server-side when they want to. They just chose not to do it for the seen indicator by default.

The closest thing to a real fix is on your side: set your account to private and regularly clean your followers list. Remove inactive accounts, people you don’t recognize, accounts that don’t feel right. A private account means only approved followers can see your stories. That’s actual access control, enforced server-side. The server won’t serve your story to someone who isn’t on your followers list. No browser extension can bypass that. No cancel: true can get around a server that refuses to send the content in the first place. That’s the difference between a privacy feature that works and one that doesn’t: server-side enforcement vs. client-side trust.

If your account is public, assume everything you post is seen by everyone. Don’t post anything you wouldn’t want a stranger to screenshot, save, and keep forever. That was always the reality of public social media, but the “seen” indicator gave people the illusion of control. It made you think you knew who was watching. You didn’t. You never did.

None of this excuses Meta. A privacy indicator that doesn’t work is worse than no indicator at all. It creates a false sense of security. People made real decisions based on the viewer list: who to block, who to trust, whether they were being watched. Those decisions were based on incomplete data, and Meta knew it.

Source code and technical details: instagram-story-research