Stripe Integration: How I Solved a Stripe and Firebase Authentication Bug That Was Causing Paying Customers to Hit the Paywall Again
If you’ve ever built a subscription platform using Next.js, Firebase Authentication and Stripe, you’ll know that the payment flow isn’t actually the difficult part.
The difficult part is making sure every system agrees on who the customer actually is.
Recently I worked on a client project where users were successfully paying through Stripe, yet as soon as they returned to the application they were immediately sent back to the paywall as though they’d never subscribed.
From the customer’s perspective it looked like the payment had failed.
From Stripe’s perspective everything was working perfectly.
The real problem was hidden inside the identity mapping between Firebase Authentication and Stripe.
After tracing the entire authentication flow, I found a subtle issue that I’ve actually seen across several applications over the past few years.
This article walks through exactly what happened, why it happened, and how I fixed it.
Stripe Integration: The Technology Stack
This particular platform was built using a modern JavaScript stack consisting of:
- Next.js
- Firebase Authentication
- Firebase Firestore
- Stripe Checkout
- Stripe Webhooks
- Node.js API routes
It’s a very common architecture because each component does one job extremely well.
Next.js delivers a fast frontend and server-side rendering.
Firebase Authentication manages user identities.
Stripe handles payments and recurring subscriptions.
The challenge is ensuring all three systems stay synchronised.
What is Next.js?
Next.js is a React framework designed for building fast, scalable web applications.
Rather than rendering everything inside the browser, Next.js can render pages on the server, pre-build pages during deployment, or generate them dynamically as users request them.
For subscription platforms this provides several advantages:
- Fast page loads
- Excellent SEO
- API routes running on the server
- Secure backend logic
- Easy deployment to Vercel or AWS
In this client’s application, Next.js was responsible for rendering the interface, managing authenticated routes and communicating with both Firebase and Stripe.
However, Next.js wasn’t actually causing the bug.
It simply exposed the issue because it relied on Firebase to determine whether someone should have access to premium content.
What is Firebase Authentication?
Firebase Authentication is Google’s managed authentication platform.
Rather than building your own login system, password encryption, account recovery and session management, Firebase handles everything for you.
Users can sign in using:
- Email and password
- Apple
- Microsoft
- Anonymous accounts
Every authenticated user receives a unique Firebase UID.
This UID is incredibly important because it becomes the permanent identifier for that customer.
Even if they change their email address later, the Firebase UID never changes.
Most applications use this UID as the primary key for:
- User profiles
- Preferences
- Subscription status
- Firestore documents
- Permissions
Think of it as the customer’s digital fingerprint.
Where Stripe Fits Into the Picture
Stripe has its own customer model.
Every customer receives:
- Customer ID
- Payment methods
- Subscription records
- Invoice history
- Billing information
Stripe doesn’t know anything about Firebase.
Firebase doesn’t know anything about Stripe.
It’s the application’s responsibility to connect those two identities together.
Normally this works like this:
- User signs up
- Firebase creates a UID
- User starts checkout
- Stripe creates or finds a Customer
- Payment succeeds
- Webhook fires
- Subscription is attached to the Firebase user
When everything lines up, the customer instantly receives access.
The Original User Journey
The application allowed anyone to create a free account before deciding whether to purchase a subscription.
The flow looked something like this:
- User signs up with their Gmail account.
- Firebase creates a UID.
- User explores the application.
- User decides to subscribe.
- User enters Stripe Checkout.
- Stripe processes payment.
- Subscription unlocks premium features.
At least, that was the theory.
In reality something slightly different was happening. You can see in the graph here:

The Problem
Many users weren’t completing Stripe Checkout using the same email address they originally used to create their account.
Sometimes they manually entered another email.
Even more commonly, Stripe automatically populated a different email using browser cookies from previous purchases.
For example:
The user signs up with:
Firebase creates:
UID: xY82Kd9Ab
Later the user reaches Stripe Checkout.
Stripe automatically remembers:
The user doesn’t notice and simply completes payment.
Now the application has two completely different identities.
Firebase thinks the customer is:
Stripe thinks the customer is:
From a human perspective they’re obviously the same person.
From the software’s perspective they’re two entirely different users.
Why This Broke the Subscription Logic
The application relied on matching the Stripe customer back to the authenticated Firebase user.
Because the emails no longer matched, the membership lookup failed.
When the webhook completed, it successfully created the subscription inside Stripe.
However, when the application later asked:
“Does this logged-in Firebase user have an active subscription?”
The answer appeared to be:
“No.”
The subscription existed.
The payment existed.
The Firebase account existed.
They simply weren’t connected.
The Customer Experience
This created a terrible user experience.
Imagine paying for Netflix and immediately seeing:
Subscribe Now
again.
Customers genuinely believed:
- the payment failed
- they had been charged twice
- the website was broken
- support needed to refund them
Naturally this generated a significant number of support tickets.
Each ticket required someone to manually investigate:
- Stripe
- Firebase
- Firestore
- Webhooks
- Customer emails
None of which should have been necessary.
Why Stripe Cookies Were Part of the Problem
One thing many developers don’t realise is that Stripe Checkout tries to make purchasing easier by remembering previous customers.
If you’ve previously purchased from another website using Stripe, it may automatically suggest or pre-fill that email address.
Usually that’s incredibly helpful.
In this situation it created an identity mismatch.
Stripe wasn’t doing anything wrong.
Firebase wasn’t doing anything wrong.
They were simply operating independently.
How I Fixed It
Rather than allowing Stripe Checkout to determine the customer’s email address, I made the authenticated Firebase session the single source of truth.
Once the user logged into the application, their authenticated email became authoritative.
Instead of allowing Stripe to:
- autofill another email
- accept an alternative customer email
the application supplied the authenticated email directly into Stripe Checkout.
That meant:
Firebase Email
↓
Stripe Checkout
↓
Stripe Customer
↓
Subscription
↓
Webhook
↓
Firebase UID
Everything remained perfectly aligned.
Why This Solution Works
Once every system uses the same identity, the subscription flow becomes extremely reliable.
After payment:
- Stripe webhook fires.
- Subscription is created.
- Customer ID is stored.
- Firebase UID is updated.
- Membership lookup succeeds.
- Premium content unlocks immediately.
No duplicate accounts.
No mismatched subscriptions.
No unnecessary support tickets.
Lessons Learned
One of the biggest lessons from this project is that payment gateways are rarely the real problem.
Stripe itself was functioning exactly as designed.
The issue was the way identity flowed between multiple systems.
Whenever you’re integrating:
- Stripe
- Firebase
- Next.js
- Auth providers
- Webhooks
it’s essential to decide which platform owns customer identity.
In my opinion, authentication should always be the source of truth.
Everything else should map back to that authenticated user.
This Isn’t the First Time I’ve Seen This
Interestingly, this isn’t the first application where I’ve encountered this issue.
I’ve seen similar problems involving:
- Firebase
- Supabase
- Auth0
- Clerk
- Stripe Billing
The symptoms are almost always identical.
Customers pay successfully.
Subscriptions exist.
Yet users still hit the paywall because multiple systems disagree about who they are.
These bugs can be frustrating because nothing actually appears “broken.” Every platform reports success. The challenge lies in tracing the data flow across authentication, checkout, webhooks and the database until you find the point where identities diverge.
Final Thoughts
Subscription applications live or die by the onboarding experience. If a customer has just entered their credit card details, completed payment and is still being asked to subscribe again, trust disappears almost instantly.
By making Firebase Authentication the single source of truth and ensuring Stripe always uses the authenticated user’s email address, we eliminated the identity mismatch completely. Support tickets dropped, customers gained access immediately after payment, and the subscription journey became seamless.
If you’re building a SaaS platform, membership site or mobile application using Next.js, Firebase and Stripe, and you’re running into issues with subscriptions, webhooks, authentication or users being incorrectly paywalled, these are exactly the kinds of problems I enjoy solving. Sometimes the hardest bugs aren’t caused by one platform failing, but by several excellent platforms not speaking the same language. Getting those systems working together reliably is where the real engineering happens.
