By Uladzimir Burylau, Engineering Manager
When we set out to build Anonymous Mode for Flo, one question stumped us for weeks: How do you let Anonymous Mode users pay for premium features? It sounds simple until you realize that payment systems are fundamentally identity systems.
App Store subscriptions are tied to your Apple ID, and Google Play subscriptions link to your Google account. Stripe and PayPal explicitly require identifying information because that's how they prevent fraud. Every major payment platform is designed around knowing exactly who is paying for what.
Online, subscription IDs might look like random strings of numbers and letters. But they’re unique — and uniqueness can create a breadcrumb trail. If connected to other records, those identifiers can slowly chip away at anonymity.
That was the challenge we had to solve.
If Anonymous Mode is going to truly mean anonymous — where no one, not even us, can identify you — then we can’t create records that connect an anonymous account to payment details. For example, we can’t keep a database entry that says, “Anonymous user X has subscription Y from payment provider Z.”
A record like that would create a trail. And any trail creates the possibility that someone — whether it’s law enforcement or a malicious actor — could follow it and eventually connect the account to a real person.
That would defeat the whole purpose of Anonymous Mode.
For Anonymous Mode, that simply wasn’t good enough. We couldn’t rely on traditional subscription tracking methods without risking a link back to someone’s identity. So we built a different approach from the ground up. Here’s the challenge we faced, what we created to solve it, and why it protects your anonymity.
The Problem: Payment Providers Require Identity
To understand the challenge, it helps to look at how subscriptions normally work through the App Store.
Here’s the typical process via the Apple Store:
- You make a purchase in the Flo app.
- The App Store processes the payment, which is tied to your Apple ID and payment method.
- The App Store sends Flo a transaction ID to confirm the purchase.
- Flo stores a record linking your Flo user ID to that transaction ID, so we know your subscription is active. I.e. {flo_user_id: "abc123", transaction_id: "xyz789", subscription_status: "active"}
- Apple then notifies Flo about renewals or cancellations, and we update your subscription status.
When not using Anonymous Mode, this works exactly as intended.
But in Anonymous Mode, step 4 creates a problem. Storing a record that links a Flo user ID to an external transaction ID creates a connection between two systems.
Here’s why it matters:
- The Flo user ID connects to app activity — cycle logs, symptoms, pregnancy tracking, and other health data. (i.e. xyz789) connects back to Apple. Apple knows which Apple ID made the purchase, and that Apple ID is tied to personal details like a name, email address, payment method, and device.
- The Flo user ID (i.e. abc123) connects to app activity — cycle logs, symptoms, pregnancy tracking, and other health data.
- When those two identifiers are stored together, they create a bridge between identity and health information.
Under most circumstances, that’s simply how app store payments function. But in a rare worst-case scenario — such as a legal request — those linked records could potentially be used to identify someone.
However, this would undermine Anonymous Mode. We couldn’t allow the payment system to become a backdoor that reconnects identity and health data. And we also didn’t want to restrict Anonymous Mode only to non-subscribers.
So we had to rethink how subscriptions could work — without ever creating that identifying link in the first place.
Why the Obvious Solutions Don't Work
Before I explain what we built, let me explain why some of the most obvious solutions fail:
- "Just don't store the transaction ID once you have recorded a premium subscription."
That raises an obvious challenge. We still need a way to confirm whether a subscription is active. Renewals, cancellations, refunds, and expired payment methods all have to be handled correctly.
In other words, the system still needs a reliable way to verify subscription status — without creating a link that compromises anonymity.
- "Use a separate anonymous payment processor."
There isn't one. Every payment processor we evaluated (and we looked at many) requires identity verification for anti-fraud reasons. They need to know who they're charging. Even crypto payment processors have KYC requirements for recurring payments.
- "Hash the transaction ID before storing it."
Payment providers use transaction IDs in their APIs—we need to send them the real ID to verify status. A hash doesn't work because we can't reverse it to check with the provider, and if we could, this means our users are not anonymous. And sophisticated measures, such as rainbow tables would make it easy for anyone with access to both systems to break hashes.
- "Use separate accounts for billing."
This just moves the problem. Now we're linking "billing account X" to "anonymous user Y," and billing account X is still tied to a real identity through the payment provider.
Every standard approach either requires storing a linkable identifier or makes basic subscription management impossible.
Our Solution: Transient Verification
We decided to verify subscription status transiently when needed, without storing any persistent link between Flo user IDs and external transaction IDs. This means that Flo checks whether someone has an active subscription only at the moment it is needed, and then lets that information go.
Flo does not keep a record that connects a Flo user to a transaction, so there is no lasting link between identity and subscription.
Here’s what the architecture looks like at a high level:
When an individual subscribes:
- User makes a purchase through App Store/Google PlayThe payment provider processes the payment and returns the transaction ID to the client (i.e. your browser or app)
- Client sends transaction ID and user ID to Flo servers (our backend system where we process and store data)
- Flo server verifies transaction with payment provider ("Is transaction ID xyz789 valid and active?")
- Flo updates user's subscription status in database: {flo_user_id: "abc123", subscription_status: "premium", subscription_source: "app_store"}
- Critical part: Flo’s servers NEVER stores user ID and transaction ID pair
- The server discards transaction ID from memory after verification
It is like showing a wristband at the door of an event. The ticket seller gives you a wristband number, you show it to security so they can confirm it is valid, and once you’re inside, security only notes that you’re allowed in. They do not keep a copy of your ticket number (i.e. the transaction ID). After the check, the wristband number is forgotten and they do not know your movements when you're inside the venue.
What gets stored persistently:
- Flo user ID: abc123
- Subscription status: premium or free
- Subscription source: app_store, google_play, stripe, or paypal
- Expiration date: When the subscription is set to expire
What NEVER gets stored:
- The actual transaction ID from the payment provider linked to user ID
- Any identifier that links to the external payment system
- Connection between the Flo user and the payment provider identity
The verification occurs transiently (in memory during the API request), and the linking information is discarded afterward.
Why This Works
We only need to confirm a subscription when someone is actively using the app. There’s no need to maintain a permanent database record linking subscriber IDs to transaction IDs.
Because we don’t store that connection, there’s no standing link between a Flo user and their payment identity. The only time both pieces of information appear together is during live verification — and even then, they exist temporarily in memory for the duration of a single API request before being discarded.
That design choice matters. Even in the unlikely event that someone gained full access to our database, they wouldn’t find transaction IDs tied to Anonymous Mode accounts. Without that stored link, there’s nothing to connect a user’s health data to a payment identity.
The Trade-Offs
To protect true anonymity in Anonymous Mode, we made deliberate trade-offs, which come with some limitations:
Limitation 1: No Cross-Platform Subscriptions
You can't buy a subscription on Google Play and use it on iOS with Anonymous Mode. Here's why:
- You subscribe on Android through Google Play
- Your Android device has the Google Play transaction ID
- That transaction ID only works with Google Play's verification API
- If you switch to iOS, your iPhone will not have that Google Play transaction ID to send
- Without the transaction ID, we can't verify the subscription
In regular mode, we store the transaction ID in our database, so it follows you across devices. In Anonymous Mode, we deliberately don't store it, so it can't follow you.
Limitation 2: Can't Update Status in Background
With traditional subscriptions, payment providers send us webhooks such as "Subscription xyz789 was cancelled" or "Payment method for abc123 expired." We update our database automatically.
With Anonymous Mode, we don't store those mappings. When Apple sends us "transaction xyz789 cancelled," we don't know which Flo user that corresponds to. We can't update their status until they open the app. When the app is opened, the transaction ID stored on the device is briefly checked using the transient verification process described above.
This means subscription status might be slightly delayed. For example, if your payment fails and Apple cancels your subscription, you'll still technically have Premium features until the next time you open the app and we verify the change.
Limitation 3: Analytics Challenges
We can't analyze subscription metrics as easily. Questions like "How many users who subscribed in January are still subscribed?" requires linking individual users to specific subscription transactions over time. Without stored mappings, we can only see aggregate numbers such as: "X users currently have premium status."
For financial reporting and business intelligence, this is a genuine limitation, but it's a limitation we intentionally accept. We believe everyone should be able to track their health in a way that feels safe and comfortable to them, and protecting that choice matters more than any analytics or reporting needs.
The Upside: Subscription Portability
Interestingly, this architecture enables subscriptions to be transferred when a user switches to Anonymous Mode.
Here's a scenario that's possible now:
- User subscribes to Flo Premium not in Anonymous Mode
- User switches to Anonymous Mode, creating a new Flo account
- The user's device still has the App Store transaction ID from when they subscribed to Premium before using Anonymous Mode
- The new Anonymous Mode account is verified transiently with the same transaction, before the transaction ID is discarded
- Premium features work on the new anonymous account
This happens because we verify using the transaction ID provided by the device, not the Flo account that originally purchased it. As long as the device has the transaction ID (which it does, since it's stored in the App Store/Google Play), any Flo account signed in on that device can use that subscription. The transaction "follows" the device and payment provider account (Apple ID / Google account), not the Flo account.
This is actually good for privacy. It means users can switch between normal and Anonymous Mode without losing their subscription.
Implementation: The Verification Flow
Let me walk through the actual architecture, which is detailed in our technical whitepaper.
When verification happens:
[Flo App] → Sends transaction ID + user ID → [Flo Server]
↓
[Verification Process in Memory]
1. Call App Store API with transaction ID
2. App Store confirms: "Yes, active subscription"
3. Extract subscription details (expiration, status)
4. Update Flo user record with status
5. Discard transaction ID from memory
↓
[Subscription Storage - No Transaction IDs]
Stores only: user_id, status, source, expiration
↓
[Flo App] ← Returns subscription status
The critical architectural point: the verification logic lives in application code (RAM), not in persistent storage (database). When the request completes, the link between the user ID and the transaction ID is lost.
Why Payment Providers Can't Identify Users
You might ask: "Can't law enforcement just ask Apple to identify everyone with an active Flo subscription, then correlate that with Flo's user activity?"
They could try, but here's why it wouldn’t work:
What Apple knows:
- Apple ID xyz@email.com has an active subscription to Flo
- Payment history for that Apple ID
What Flo knows:
- Anonymous user abc123 has premium status
- Health data logged by user abc123
- That premium status was verified with the App Store. But which specific transaction ID ties back to xyz@email.com? We don’t know — because the transaction ID is only used briefly to confirm the purchase and is never stored.
What's missing:
- No stored link between abc123 and any Apple transaction ID
- No stored link between abc123 and xyz@email.com
- No way to connect the health data logged in abc123’s account to their identity
Even if Apple provided a list of every Flo subscriber's identity, we couldn't match them to our anonymous user records. The linking information exists only transiently during verification and is never stored.
The only way to connect them would be:
- Apple identifies: "xyz@email.com subscribes to Flo"
- Apple monitors: "xyz@email.com's devices are making App Store API calls to verify Flo subscription right now"
- At that exact moment, someone with access to Flo's application servers observes: "Anonymous user abc123 just sent a verification request."
- Correlation: Both happened at the same time from the same device
Making that connection would require real-time surveillance of both systems at the same moment. While theoretically possible, it would demand an extraordinary level of coordination that goes far beyond standard legal or technical processes.
It would also mean monitoring all relevant App Store subscription activity in real time and catching the exact moment a user’s subscription is verified. There’s no way to predict when — or even if — a user will trigger that verification. It could happen rarely, or not at all.
And even in that narrow window, the opportunity is fleeting. Once the verification is complete, the transaction ID is discarded and the temporary connection disappears.
The Bigger Picture
Subscription handling is just one part of Anonymous Mode’s overall design. But it illustrates a bigger point: real privacy by design means examining every part of the system — not just adding a privacy layer on top. We couldn’t simply bolt privacy onto our existing subscription logic. We had to rethink it from the ground up: what information is stored, when verification happens, what isn’t retained, and which trade-offs we’re willing to make to protect anonymity.
That same approach applies across Anonymous Mode. From subscriptions to Partner Mode, and even smaller features — like free-text notes that could accidentally reveal identifying details — each decision was evaluated through a single lens: does this preserve true anonymity?
If it didn’t, we redesigned it or removed it.
Conclusion
At first glance, payments and anonymity can seem incompatible. Most payment systems are built around identity — verifying who someone is and tying transactions to an account.
But with careful system design, it’s possible to accept payments from anonymous users without creating a stored link between payment identity and app identity. For us, the solution was transient verification. We briefly connect the necessary pieces — like a user ID and a transaction ID — only at the moment we need to confirm a subscription. Once that verification is complete, the link is discarded and not retained in our systems.
This approach does come with trade-offs. It requires more complex engineering and limits certain conveniences. But for people who need anonymous access to premium features, those trade-offs are intentional and worthwhile. The subscription should never become the thread that unravels their anonymity.
For anyone building privacy-focused products, this is the key lesson: payments can’t be an afterthought. If anonymity is the goal, you have to design subscriptions from first principles — and ensure they don’t quietly undermine everything else.
Related Resources: