A Founder's Guide to the Stripe Subscription API for Mobile Apps
Learn to use the subscription API Stripe to build recurring revenue in your mobile app. A founder's guide with React Native and Node.js examples.
By Sanket Sahu
28th Mar 2026

When you're building a subscription feature for your mobile app, it’s easy to get lost in the code. But integrating the Stripe subscription API is less about raw code and more about understanding a system. It’s an ecosystem of Products, Prices, and Subscriptions that work together to automate your billing. Getting this right is what allows your app to scale, test different price points, and manage the entire customer journey without manual intervention.
Laying the Groundwork for Your Subscriptions

Before a single line of code is written, the most critical work happens in your Stripe Dashboard. This is where you translate your business model into Stripe's language. I've seen too many product teams rush this step, only to get tangled in technical debt later when they want to change their pricing.
Think of this as creating the strategic foundation for your revenue engine. The choices you make here will either fuel your growth or become a source of friction for you and your customers. Taking the time to get this right from the start is non-negotiable.
Define Your Products and Prices
First, you need to model what you're selling using Stripe’s Products and Prices. This is a simple but powerful concept that gives you tremendous flexibility.
A Product is the what—the core service or plan you're offering. For example, a "Pro Plan" for your fitness app or a "Team Workspace" for your collaboration tool. A Price is the how—how much you charge and how often.
The magic is that a single Product can have many different Prices. Let's say your "Pro Plan" Product offers premium features. You could easily attach multiple Prices to it:
- $10 billed monthly
- $100 billed annually
This setup is brilliant for product managers. It lets you experiment with different billing cycles, currencies, or promotional offers without creating a messy product catalog. It keeps your business logic clean and manageable as you test and learn.
A word of advice: Treat your Stripe Product catalog as the absolute source of truth for your business model. Any change to your pricing or plans should always start here. This discipline keeps your engineering, product, and marketing teams in sync and helps you avoid a world of headaches down the road.
Choose the Right Subscription Pricing Model
Stripe's subscription APIs are incredibly versatile and can handle just about any pricing model you can dream up. The key is to pick the model that best aligns with the value your app delivers to the customer.
Here’s a quick rundown of the most common models I see used for mobile apps and when they make the most sense.
Stripe Subscription Pricing Models at a Glance
| Pricing Model | Best For | Example Use Case |
|---|---|---|
| Per-Seat (Per-Unit) | B2B or team-based apps where value scales with the number of users. | A project management app charging $15/month for each user added to a team workspace. |
| Tiered Pricing | Apps with distinct feature sets for different customer segments (e.g., free, pro, enterprise). | A meditation app with a free tier for basic sessions and a premium tier for guided programs and offline access. |
| Usage-Based (Metered) | Apps where cost is directly tied to consumption of a resource. | A video editing app charging based on the number of minutes of video exported each month. |
Choosing the right model is a core part of your growth strategy. There’s a reason Stripe's recurring billing products have become a cornerstone for so many SaaS and subscription companies—they’re built for this kind of strategic flexibility.
Lock Down Your API Keys and Webhooks
Finally, let's cover a couple of foundational security steps you absolutely cannot skip. Inside your Stripe Dashboard, you'll find your API keys. It is critical to understand the difference between them.
Your Publishable Key is meant to be used in your mobile app's client-side code—it’s safe to expose. Your Secret Key, on the other hand, must be guarded fiercely. It should only ever be used on your server and never, ever make its way into your frontend code.
You'll also need to configure a webhook endpoint. This is just a secure URL on your server that Stripe uses to send you real-time notifications about events, like invoice.paid or customer.subscription.deleted. We'll dive deep into handling these later, but for now, just get the endpoint set up in your Dashboard.
As a final housekeeping tip, think about how this financial data will flow into your accounting software. For example, you might want to Connect Stripe to Xero to automate your bookkeeping. Getting these operational pieces in place early on will save you a ton of manual work later.
Building a Seamless React Native Subscription UI
A clunky or untrustworthy checkout flow is a guaranteed conversion killer. For any mobile app, the subscription UI isn't just a form—it's the moment of truth. This is where a user decides whether your service is worth trusting with their payment information. Getting it right is non-negotiable.
Our go-to tool for this is the official Stripe React Native SDK. It provides pre-built, secure UI components that handle the messy parts of payment collection, saving you a massive amount of development time while ensuring you stay PCI compliant. The real star of the show is the PaymentSheet.
The PaymentSheet: Your UI Centerpiece
The PaymentSheet is a slick, all-in-one UI that slides up from the bottom of the screen. It lets users securely enter card details, use saved cards, or pay with mobile wallets like Apple Pay and Google Pay. The best part? Stripe contains all the sensitive data within its own secure environment, so it never even touches your server or app.
The process is a simple back-and-forth between your app and your backend. Your app first asks your server for the credentials it needs to show the payment form. In response, your server talks to Stripe to create a Customer and a SetupIntent, then sends the necessary keys back to your app.
This flow gives your frontend everything it needs to present a secure payment form without ever handling raw card numbers. For a closer look at the initial SDK configuration, check out our guide on Stripe in React Native.
Once your app has the credentials, it's ready to initialize and present the PaymentSheet. From there, the SDK takes the wheel.
For founders and product managers, here's the key takeaway: using Stripe's pre-built UI isn't a shortcut; it's a best practice. It delivers a native, trustworthy user experience that has been battle-tested across millions of transactions. This directly translates to higher conversion rates for your subscriptions.
Handling All Possible User Actions
A polished UI anticipates every outcome. When a user interacts with the PaymentSheet, your code needs to be prepared for more than just a successful payment.
After your server provides the client secret and other details, you’ll call the initPaymentSheet and presentPaymentSheet functions from the SDK. The presentPaymentSheet function is what you'll want to watch, as it returns a promise that tells you exactly what happened.
Here are the main scenarios you need to handle:
completed: Success! The user added their payment method. Your app should show a quick confirmation and then call your backend to officially create the subscription.canceled: The user tapped the "close" button or swiped the sheet away. This isn't an error. Your UI should simply return to the pricing screen without any scary error messages.failed: Something went wrong. It could be a network hiccup or a problem with the details your server provided. You have to catch this error, log it for your own debugging, and show the user a friendly message like, "Something went wrong, please try again."
If you want to sharpen your skills for implementing these flows, exploring more about building apps with React Native can give you a stronger foundation. A solid grasp of the framework makes integrating these crucial pieces much more straightforward.
By thoughtfully managing these states, you build a robust and professional user journey. This attention to detail is what separates a basic technical implementation from a great product experience that builds confidence right from the start.
The Backend's Role: Your Subscription Command Center
While your React Native app handles the pretty frontend, your backend is where the real work happens. This is the secure command center that drives your entire subscription system. For this guide, we'll be working with a Node.js and Express server, which will talk directly to the subscription API Stripe provides.
Think of your server as having three core responsibilities in the initial subscription flow:
- Creating a Stripe Customer: Every subscription is tied to a
Customer. This Stripe object is how you'll save payment methods and track that user's billing history. - Generating secure credentials: Your app needs a special key, the
client_secret, to initialize Stripe’sPaymentSheetUI. Your server is responsible for creating this temporary key. - Activating the subscription: After the user enters their payment details on the app, your server makes the final call to Stripe to officially create the
Subscriptionand start the recurring billing.
This server-centric approach is non-negotiable for security. It ensures your secret API keys stay safely on your server and are never, ever exposed in the mobile app's code.
Here's a high-level look at how the pieces fit together, from the user choosing a plan to a successful payment.

This flow really drives home the separation of concerns: the client handles the user interface, while the server manages the sensitive, secure operations.
Setting Up a Customer and Preparing for Payment
Before you can charge anyone, you need to create a Customer object in Stripe. This is typically done the first time a user interacts with your paid features or even right after they sign up. Once created, you'll get back a customer_id—make sure you save this in your database and link it to your app's user record.
Now, with a Customer in place, you can prepare to collect their payment details. For subscriptions, your goal isn't a one-off charge; it's to save a payment method for future automatic billing. This is where the SetupIntent becomes essential.
A
SetupIntentsignals your intention to save a payment method for later. This is different from aPaymentIntent, which is for immediate, one-time charges. For subscriptions,SetupIntentis the right tool for the job. It securely tokenizes and attaches the payment details to the Customer, making future recurring payments possible.
Your server creates a SetupIntent and attaches it to the customer_id. Stripe’s API then returns the client_secret your mobile app is waiting for. This secret is the key that unlocks the PaymentSheet for this specific setup process.
How to Structure Your Backend Endpoints
I find the cleanest way to manage this is with a dedicated API endpoint, something like /create-subscription-intent. When your React Native app calls this endpoint, your Node.js code springs into action.
It should:
- Identify the user, usually by validating a JWT or session token.
- Use the user's ID to find their Stripe
customer_idin your database. If it doesn't exist, create a new StripeCustomeron the fly. - Create an Ephemeral Key, which gives the mobile SDK temporary, limited access to the
Customerobject. - Create the
SetupIntentfor thatcustomer_id. - Send the
client_secret(from theSetupIntent), the Ephemeral Key secret, thecustomer_id, and your publishable key back to the app.
This single endpoint handles all the prep work. If you're looking to understand how this fits into a bigger picture, you might find it helpful to read up on broader API integrations for your platform.
Creating the Actual Subscription
The user has now entered their card details and tapped "Subscribe" in the PaymentSheet. The app gets a confirmation from Stripe's SDK and immediately makes one last call to your backend, this time to a different endpoint like /create-subscription.
This endpoint needs the customer_id and the new payment_method_id that was just generated by the PaymentSheet. With those two pieces of information, your server can finally tell Stripe to create the subscription.
The call to the subscription api stripe offers looks something like this:
// Example Node.js/Express code snippet
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{ price: 'price_1MpbAbL2eZvKYlo2xYkS4kAb' }], // Your Price ID
default_payment_method: paymentMethodId,
expand: ['latest_invoice.payment_intent'],
});
This is the moment of truth. You're telling Stripe to start a recurring subscription for this customer using their new default payment method. You can trust this process; Stripe handles over 500 million API requests daily and has maintained 99.999% uptime, even during massive sales events like Black Friday.
Once the subscription is successfully created, the last step on your server is to update your own database—flip the is_premium flag to true for that user—and send a success response back to the app. The loop is now closed, and your user's premium plan is officially active.
How to Manage Subscriptions with Stripe Webhooks

A subscription isn't a one-and-done deal; it’s an ongoing relationship. Once the user completes the checkout, your real work begins. Over its lifetime, a subscription will renew, sometimes fail, and eventually get canceled. Your app must react to every twist and turn, which is why Stripe Webhooks are the absolute heart of your entire system.
Think of webhooks as automated messages that Stripe sends to your server, letting you know about events in your account. They are the essential communication line that keeps your app's user data perfectly in sync with the actual status of their subscription.
Without a solid webhook handler, you could easily grant premium access to a user whose payment just failed or, even worse, lock out a perfectly happy, paying customer. Let's get into how to build a resilient handler that can anchor your subscription logic.
Why You Absolutely Cannot Skip Webhooks
A common—and dangerous—mistake made by early-stage teams is to rely only on the initial successful payment. This is a trap. A subscription's lifecycle is full of events that happen completely outside of a user's direct interaction with your app.
Just think about the common scenarios:
- A monthly renewal payment gets processed automatically, deep in Stripe's servers.
- A user’s credit card expires, causing that routine renewal to fail unexpectedly.
- A customer updates their billing info through a secure, Stripe-hosted page you linked them to.
In every one of these situations, Stripe knows what happened, but your app is totally in the dark. Webhooks are the only way for Stripe to ping your server and say, "Hey, that subscription for user XYZ just renewed, so you should extend their access for another month!"
For a product team, this is what automates your revenue operations. A bulletproof webhook handler eliminates the need for manual checks on payment statuses or user access. It allows your system to run itself, which is a non-negotiable for scaling.
Setting Up Your Webhook Handler
Your first job is to create a dedicated endpoint on your server—a URL that Stripe can send POST requests to. A standard convention is something like /webhooks/stripe. You’ll then register this URL in your Stripe Dashboard under the "Webhooks" section.
Once that’s done, Stripe will start firing JSON payloads to this endpoint for all sorts of events. Your server’s responsibility is to listen for these requests, figure out what they mean, and act accordingly.
When building this, there are two best practices you can't afford to ignore: signature verification and idempotency.
- Signature Verification: You have to be certain a request is actually from Stripe and not a malicious actor trying to get free service. Stripe includes a unique
Stripe-Signatureheader in every webhook request. Using your webhook’s signing secret (which you get from the Stripe Dashboard) and the Stripe SDK, you can confirm the event is authentic. If that signature doesn't check out, you must ignore the request. - Idempotency: Networks aren't perfect. Sometimes Stripe might send the same event more than once. Your handler needs to be built to handle this without causing chaos. For instance, if you get an
invoice.paidevent for an invoice you’ve already processed, your code should be smart enough to recognize it and just skip it, rather than giving a user double the access time.
Handling Essential Subscription Events
Stripe has hundreds of event types, but for a mobile app subscription, you only need to focus on a critical few to get started. Getting these right will cover over 90% of your subscription management needs.
For a robust subscription system, you'll need to handle several key webhook events to keep your app's state in sync with Stripe. The table below outlines the most important ones, what they signify, and what action you should take in your backend.
Essential Stripe Webhook Events for Subscriptions
| Webhook Event | What It Means | Required Action in Your App |
|---|---|---|
invoice.paid | A payment was successfully made for an invoice (either the initial one or a renewal). | Update your database to grant or extend the user's access. Set their premium_until date to the end of the new billing period. |
invoice.payment_failed | A payment for an invoice failed (e.g., insufficient funds, expired card). | Restrict access to premium features and send the user an in-app notification or email prompting them to update their payment method. |
customer.subscription.deleted | The subscription was canceled, either by the user or after too many failed payments. | This signifies churn. Revoke access to premium features in your database and maybe trigger a "we're sorry to see you go" email flow. |
Focusing on these three events provides a solid foundation for managing the entire customer lifecycle automatically.
Here’s a glimpse of what the core logic looks like in a Node.js/Express handler. Notice how it verifies the signature right away and then uses a switch statement to route the event to the right function.
// Example Node.js/Express webhook handler
app.post('/webhooks/stripe', express.raw({type: 'application/json'}), (request, response) => {
const sig = request.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(request.body, sig, webhookSecret);
} catch (err) {
// On error, return a 400 error to Stripe
return response.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle the event
switch (event.type) {
case 'invoice.paid':
handleInvoicePaid(event.data.object);
break;
case 'invoice.payment_failed':
handleInvoicePaymentFailed(event.data.object);
break;
case 'customer.subscription.deleted':
handleSubscriptionDeleted(event.data.object);
break;
// ... handle other event types
default:
console.log(`Unhandled event type ${event.type}`);
}
// Return a 200 response to acknowledge receipt of the event
response.send();
});
Building out this part of your subscription API Stripe integration is more than just a technical step. It’s what creates a reliable, automated system that correctly manages the customer lifecycle, protects your revenue, and makes sure your users always have the access they've paid for.
Letting Users Manage Their Own Subscriptions
Giving users a way to manage their own subscriptions is one of those things you learn to prioritize very quickly. It's not just a nice-to-have feature; it's a powerful tool for cutting down on customer churn and dramatically reducing your support team's workload. When people can easily change their plan or cancel without having to email you, they feel in control.
You're basically at a fork in the road here. You can either build a custom management interface directly into your app, or you can integrate the pre-built Stripe Customer Portal. There's no single right answer—the best choice really boils down to your product's maturity and your team's resources.
The Custom-Built Management UI
Going the custom route gives you total control over the user experience. You can design a subscription management screen that feels completely native to your app, matching its branding and flow perfectly. This path offers the most flexibility, but be prepared—it also requires a significant development effort.
The typical flow starts on a settings screen inside your app, where you’ll have buttons like "Change Plan" or "Cancel Subscription." When a user taps one, your app makes a call to a dedicated backend endpoint. From there, your server gets to work, using the subscription API Stripe provides to make the requested changes.
This approach is perfect for mature products that demand a tightly integrated experience or have complex subscription logic that a pre-built solution just can't handle. If you're building a highly customized experience, check out our guide on creating a membership website builder for more ideas on structuring these user flows.
Handling Upgrades and Downgrades
When a user decides to switch plans, your server just needs to perform a subscription update. I've found this to be surprisingly straightforward. You simply fetch the existing Subscription object and modify its items to reflect the new plan.
The best part? Stripe handles all the proration automatically. It figures out the credit for any unused time on the old plan and applies it to the cost of the new one.
- Upgrades: The user is billed a prorated amount immediately for the time remaining in the current billing cycle on the more expensive plan.
- Downgrades: A credit for the unused time is created and applied to future invoices, reducing the cost of their next bill (or bills).
This built-in proration logic is a lifesaver. It keeps billing fair and transparent, which goes a long way toward preventing customer confusion and those dreaded "Why was I charged this?" support tickets.
My advice is to always be explicit with the user about what will happen. In your UI, before they confirm the change, show them a clear message like, "You will be charged a prorated amount of $X.XX today," or "A credit will be applied to your next bill." This transparency builds trust.
Integrating the Stripe Customer Portal
For teams that need to move fast and don't want to reinvent the billing wheel, the Stripe Customer Portal is a fantastic option. It's a secure, Stripe-hosted page where your users can manage their subscriptions, update payment details, and view their entire billing history.
The real beauty of the Customer Portal is how fast you can get it working. Your backend makes a single API call to create a "Portal Session" for a specific Stripe customer_id. Stripe sends back a unique URL, and your app simply needs to open that URL in a web view or the device's browser. That’s it.
This approach offloads all the UI development and long-term maintenance to Stripe. It's a perfect fit for:
- MVPs and early-stage products: Launch a full-featured subscription management area in hours, not weeks.
- Teams with limited resources: Keep your engineers focused on your core product, not on building and maintaining a billing UI.
- Standard subscription models: If your plans are straightforward, the portal gives you everything you need right out of the box.
The main trade-off here is control over the experience. You can customize the branding with your logo and brand colors, but the layout and core functionality are managed by Stripe. For most mobile apps, especially in the early days, this is a more than acceptable compromise for the speed and security you get in return.
Testing Your Integration and Handling Real-World Errors
Let's be honest: a broken payment flow is one of the quickest ways to kill user trust and sink your revenue. Once you've wired up your subscription logic, testing can't be an afterthought. It's the critical phase where you prove your integration can actually handle the messy reality of billing. This means going way beyond a single successful payment to simulate the entire subscription lifecycle.
The great thing is, Stripe gives you an incredible testing toolkit right out of the box. You don't have to wait weeks to see if your renewal logic works or if your dunning emails go out correctly. You can simulate all of it in just a few minutes.
Simulating Time with Test Clocks
The single most powerful tool you have for this is the Test Clock. A test clock is a special object that lets you fast-forward time within Stripe's test environment. For subscriptions, where so much is time-based, this is an absolute game-changer.
Instead of waiting 30 days for a renewal, you can just create a test clock, advance it by a month, and watch your invoice.paid webhook fire almost instantly.
Test clocks are perfect for scenarios like these:
- Trial Periods: Simulate a 14-day trial ending to make sure the first charge goes through as expected.
- Subscription Renewals: Jump the clock forward one billing cycle to confirm your
invoice.paidwebhook properly extends the user's access. - Late Payments: Fast-forward past a due date to check if your
invoice.payment_failedlogic and user notifications kick in correctly.
Getting started is straightforward. You create a test clock through the API and then associate a new test customer with it. From then on, any subscription you create for that customer will run on the clock's accelerated timeline.
Triggering Specific Outcomes with Test Cards
While test clocks handle the passage of time, you also need to simulate what happens when payments actually succeed or fail. For that, Stripe provides a whole set of special test card numbers. These are designed to trigger predictable, specific responses, from successful charges to various kinds of declines.
My go-to strategy is to create a cheat sheet of these test cards for the whole team. It saves a ton of time during development and QA, ensuring everyone can reliably test both the happy path and all the frustrating edge cases.
At a minimum, you should be using these key test cards:
| Card Number Ending In | Outcome | Why It's Important to Test |
|---|---|---|
| ...4242 | Successful Payment | This is your happy path. It verifies that the entire end-to-end flow works. |
| ...0002 | Insufficient Funds | This is the most common reason for failure. It's crucial for testing your invoice.payment_failed webhook and user notification flow. |
| ...0001 | Card Declined | A generic decline. Use this to confirm your app shows a friendly error and prompts the user to try another card. |
By methodically combining test clocks and test cards, you can build real confidence that your integration is solid. This proactive testing prevents your actual users from hitting frustrating payment walls, protecting your revenue and ensuring a smooth, professional experience right from launch.
Common Questions When Building with Stripe Subscriptions
As you get deeper into your Stripe integration, a few common questions always seem to pop up. Let's walk through some of the practical concerns I see most often from founders and product teams, both during the initial build and after the app goes live.
Can I Offer a Free Trial With the Stripe Subscription API?
Yes, and Stripe makes this surprisingly simple. You can set up free trials directly in the Stripe Dashboard when creating your products and prices. Just define how long the trial should be—say, 7 or 14 days—and Stripe handles the rest. It will automatically convert the trial to a paid subscription when the time is up, so you don't have to write any extra logic for that transition.
One piece of advice I always give: listen for the
customer.subscription.trial_will_endwebhook. It's the perfect trigger to send users a friendly reminder a few days before their card gets charged. It’s a small touch that goes a long way in building trust and preventing support tickets.
How Do I Handle Different Tax Requirements Like VAT or Sales Tax?
Tackling taxes is often a huge headache, especially for apps with a global audience. Thankfully, this is a solved problem with Stripe Tax. You can turn it on with a single toggle in your Dashboard or by adding one line to your API calls.
Once enabled, it automatically figures out and collects the correct sales tax, VAT, or GST based on your customer’s location. This completely removes the need to build and maintain your own complex tax rules, which is an absolute lifesaver for a small team.
What's the Best Way to Handle One-Time Purchases and Subscriptions?
Many apps need to support both, and you can absolutely manage them within the same Stripe integration. The trick is to tie everything to a single Stripe Customer object, which gives each user a unified billing history.
From there, you just need to create separate flows on your backend for each type of transaction:
- For one-time purchases, you'll create a
PaymentIntent. - For subscriptions, you'll create a
Subscriptionobject. This usually involves aSetupIntentfirst to securely save the user's payment method for future renewals.
Your app's UI will guide the user to the correct flow, and your backend will hit the right Stripe endpoint. This keeps everything neat and organized for both you and your customers.
Ready to turn your app idea into reality without the long development cycles? At RapidNative, we help you build and launch real, production-ready React Native apps in minutes, not months. Go from prompt, sketch, or PRD to a shareable app instantly and start validating your ideas today. Find out more at https://www.rapidnative.com.
Ready to Build Your App?
Turn your idea into a production-ready React Native app in minutes.