Skip to main content
RapidDev - Software Development Agency
firebase-tutorial

How to Connect Firebase to Stripe for Payments

Connect Firebase to Stripe by using the official Stripe Payments Firebase Extension or building a custom Cloud Function webhook. The Extension automates customer creation, subscription syncing, and payment processing by storing data in Firestore. For custom setups, create an onRequest Cloud Function that receives Stripe webhook events, verifies the signature, and updates Firestore accordingly. Always store your Stripe secret key using defineSecret() and verify webhook signatures to prevent forged requests.

What you'll learn

  • How to install and configure the Stripe Payments Firebase Extension
  • How to build a custom Stripe webhook handler with Cloud Functions
  • How to sync Stripe customer and subscription data to Firestore
  • How to securely store Stripe API keys using Cloud Secret Manager
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate8 min read20-30 minFirebase Blaze plan, Cloud Functions v2, Stripe API, firebase-functions 4.x+March 2026RapidDev Engineering Team
TL;DR

Connect Firebase to Stripe by using the official Stripe Payments Firebase Extension or building a custom Cloud Function webhook. The Extension automates customer creation, subscription syncing, and payment processing by storing data in Firestore. For custom setups, create an onRequest Cloud Function that receives Stripe webhook events, verifies the signature, and updates Firestore accordingly. Always store your Stripe secret key using defineSecret() and verify webhook signatures to prevent forged requests.

Integrating Stripe Payments with Firebase Using Extensions and Cloud Functions

Firebase and Stripe together power payment systems for thousands of apps. This tutorial covers two approaches: the official Stripe Payments Firebase Extension (fastest setup, best for subscriptions) and a custom Cloud Function webhook (most flexible, best for one-time payments and complex logic). Both approaches store payment data in Firestore and use Cloud Functions on the Blaze plan.

Prerequisites

  • A Firebase project on the Blaze plan with Firestore and Cloud Functions enabled
  • A Stripe account with API keys (Dashboard > Developers > API keys)
  • Firebase CLI installed and authenticated
  • Basic understanding of Cloud Functions and Firestore

Step-by-step guide

1

Install the Stripe Payments Firebase Extension

The fastest way to connect Firebase to Stripe is the official 'Run Payments with Stripe' extension. Go to the Firebase Console > Extensions > Explore Extensions and search for Stripe. Click Install and configure it with your Stripe secret key and webhook signing secret. The extension automatically creates Firestore collections for customers, products, and subscriptions.

typescript
1# Install via Firebase CLI
2firebase ext:install stripe/firestore-stripe-payments --project=your-project-id
3
4# The CLI prompts for:
5# - Stripe API key (from Stripe Dashboard > Developers > API keys)
6# - Stripe webhook signing secret
7# - Firestore collection names (default: customers, products, subscriptions)
8# - Cloud Functions location

Expected result: The Stripe extension is installed and creates Firestore collections for syncing payment data.

2

Configure Stripe webhook endpoint

After installing the extension, copy the webhook URL from Firebase Console > Functions and add it to Stripe. In the Stripe Dashboard, go to Developers > Webhooks > Add endpoint. Paste the Cloud Function URL and select the events to listen for. At minimum, subscribe to checkout.session.completed, customer.subscription.created, customer.subscription.updated, and customer.subscription.deleted.

typescript
1// Stripe webhook URL format (from Firebase Console > Functions):
2// https://us-central1-your-project.cloudfunctions.net/ext-firestore-stripe-payments-handleWebhookEvents
3
4// Events to subscribe to in Stripe Dashboard:
5// - checkout.session.completed
6// - customer.subscription.created
7// - customer.subscription.updated
8// - customer.subscription.deleted
9// - invoice.payment_succeeded
10// - invoice.payment_failed

Expected result: Stripe sends webhook events to your Firebase Cloud Function endpoint.

3

Create a checkout session from your app

With the extension installed, create a checkout session by writing a document to the checkout_sessions subcollection under the customer document in Firestore. The extension picks up the new document, creates a Stripe Checkout session, and writes the session URL back to the document. Your app redirects the user to this URL.

typescript
1import { db } from "@/lib/firebase";
2import { auth } from "@/lib/firebase";
3import {
4 collection, addDoc, onSnapshot
5} from "firebase/firestore";
6
7async function createCheckoutSession(priceId: string) {
8 const user = auth.currentUser;
9 if (!user) throw new Error("Must be logged in");
10
11 // Create a checkout session document
12 const checkoutRef = await addDoc(
13 collection(db, "customers", user.uid, "checkout_sessions"),
14 {
15 price: priceId, // Stripe Price ID from your products
16 success_url: window.location.origin + "/success",
17 cancel_url: window.location.origin + "/pricing",
18 }
19 );
20
21 // Listen for the Stripe session URL
22 onSnapshot(checkoutRef, (snap) => {
23 const data = snap.data();
24 if (data?.url) {
25 window.location.assign(data.url); // Redirect to Stripe Checkout
26 }
27 if (data?.error) {
28 console.error("Checkout error:", data.error.message);
29 }
30 });
31}

Expected result: Clicking a purchase button creates a checkout session and redirects the user to Stripe Checkout.

4

Build a custom webhook handler (alternative to extension)

If you need more control than the extension provides, build a custom Cloud Function that receives Stripe webhooks. Use onRequest to create an HTTP endpoint, verify the webhook signature with Stripe's library, and handle events by updating Firestore. Store the Stripe secret key and webhook signing secret using defineSecret().

typescript
1import { onRequest } from "firebase-functions/v2/https";
2import { defineSecret } from "firebase-functions/params";
3import { getFirestore } from "firebase-admin/firestore";
4import * as admin from "firebase-admin";
5import Stripe from "stripe";
6
7admin.initializeApp();
8const db = getFirestore();
9
10const stripeSecretKey = defineSecret("STRIPE_SECRET_KEY");
11const webhookSecret = defineSecret("STRIPE_WEBHOOK_SECRET");
12
13export const stripeWebhook = onRequest(
14 {
15 secrets: [stripeSecretKey, webhookSecret],
16 cors: false, // Stripe does not use CORS
17 },
18 async (req, res) => {
19 const stripe = new Stripe(stripeSecretKey.value());
20
21 // Verify webhook signature
22 let event: Stripe.Event;
23 try {
24 event = stripe.webhooks.constructEvent(
25 req.rawBody,
26 req.headers["stripe-signature"] as string,
27 webhookSecret.value()
28 );
29 } catch (err) {
30 res.status(400).send(`Webhook signature verification failed`);
31 return;
32 }
33
34 // Handle events
35 switch (event.type) {
36 case "checkout.session.completed": {
37 const session = event.data.object as Stripe.Checkout.Session;
38 await db.doc(`orders/${session.id}`).set({
39 customerId: session.customer,
40 status: "paid",
41 amount: session.amount_total,
42 createdAt: admin.firestore.FieldValue.serverTimestamp(),
43 });
44 break;
45 }
46 case "customer.subscription.updated": {
47 const sub = event.data.object as Stripe.Subscription;
48 await db.doc(`subscriptions/${sub.id}`).update({
49 status: sub.status,
50 currentPeriodEnd: new Date(sub.current_period_end * 1000),
51 });
52 break;
53 }
54 }
55
56 res.json({ received: true });
57 }
58);

Expected result: A custom Cloud Function handles Stripe webhooks and syncs payment data to Firestore.

5

Read subscription status in your app

Query Firestore to check if the current user has an active subscription. The extension stores subscription data under customers/{uid}/subscriptions. For custom webhooks, read from whatever collection you write to. Use this to gate premium features in your app.

typescript
1import { db, auth } from "@/lib/firebase";
2import {
3 collection, query, where, getDocs
4} from "firebase/firestore";
5
6async function checkSubscriptionStatus(): Promise<boolean> {
7 const user = auth.currentUser;
8 if (!user) return false;
9
10 // With Stripe Extension:
11 const subsRef = collection(
12 db, "customers", user.uid, "subscriptions"
13 );
14 const q = query(
15 subsRef,
16 where("status", "in", ["active", "trialing"])
17 );
18
19 const snapshot = await getDocs(q);
20 return !snapshot.empty;
21}
22
23// Usage:
24const isPremium = await checkSubscriptionStatus();
25if (isPremium) {
26 // Show premium features
27} else {
28 // Show upgrade prompt
29}

Expected result: Your app can check if the current user has an active Stripe subscription via Firestore.

Complete working example

functions/src/stripe-webhook.ts
1import { onRequest } from "firebase-functions/v2/https";
2import { defineSecret } from "firebase-functions/params";
3import { getFirestore } from "firebase-admin/firestore";
4import * as admin from "firebase-admin";
5import { logger } from "firebase-functions";
6import Stripe from "stripe";
7
8admin.initializeApp();
9const db = getFirestore();
10
11const stripeSecretKey = defineSecret("STRIPE_SECRET_KEY");
12const webhookSecret = defineSecret("STRIPE_WEBHOOK_SECRET");
13
14export const stripeWebhook = onRequest(
15 {
16 secrets: [stripeSecretKey, webhookSecret],
17 cors: false,
18 maxInstances: 10,
19 },
20 async (req, res) => {
21 const stripe = new Stripe(stripeSecretKey.value());
22
23 let event: Stripe.Event;
24 try {
25 event = stripe.webhooks.constructEvent(
26 req.rawBody,
27 req.headers["stripe-signature"] as string,
28 webhookSecret.value()
29 );
30 } catch (err) {
31 logger.error("Webhook signature failed:", err);
32 res.status(400).send("Signature verification failed");
33 return;
34 }
35
36 logger.info(`Stripe event: ${event.type}`);
37
38 switch (event.type) {
39 case "checkout.session.completed": {
40 const session = event.data
41 .object as Stripe.Checkout.Session;
42 await db.doc(`orders/${session.id}`).set({
43 stripeCustomerId: session.customer,
44 status: "paid",
45 amount: session.amount_total,
46 currency: session.currency,
47 createdAt:
48 admin.firestore.FieldValue.serverTimestamp(),
49 });
50 break;
51 }
52 case "customer.subscription.created":
53 case "customer.subscription.updated": {
54 const sub = event.data
55 .object as Stripe.Subscription;
56 await db.doc(`subscriptions/${sub.id}`).set(
57 {
58 stripeCustomerId: sub.customer,
59 status: sub.status,
60 priceId: sub.items.data[0]?.price.id,
61 currentPeriodEnd: new Date(
62 sub.current_period_end * 1000
63 ),
64 updatedAt:
65 admin.firestore.FieldValue.serverTimestamp(),
66 },
67 { merge: true }
68 );
69 break;
70 }
71 case "customer.subscription.deleted": {
72 const sub = event.data
73 .object as Stripe.Subscription;
74 await db.doc(`subscriptions/${sub.id}`).update({
75 status: "canceled",
76 canceledAt:
77 admin.firestore.FieldValue.serverTimestamp(),
78 });
79 break;
80 }
81 }
82
83 res.json({ received: true });
84 }
85);

Common mistakes when connecting Firebase to Stripe for Payments

Why it's a problem: Not verifying the Stripe webhook signature, allowing anyone to send fake payment events to your endpoint

How to avoid: Always call stripe.webhooks.constructEvent() with the raw request body, stripe-signature header, and your webhook signing secret. Return 400 if verification fails.

Why it's a problem: Storing the Stripe secret key in a .env file or hardcoding it in function code

How to avoid: Use defineSecret('STRIPE_SECRET_KEY') to store it in Cloud Secret Manager. Include it in the function's secrets array. Never put secret keys in .env files — they are deployed unencrypted.

Why it's a problem: Forgetting to add the Cloud Function URL as a webhook endpoint in the Stripe Dashboard

How to avoid: After deploying your function, copy its URL from Firebase Console > Functions and add it in Stripe Dashboard > Developers > Webhooks > Add endpoint. Select the specific events you want to receive.

Why it's a problem: Using req.body instead of req.rawBody for webhook signature verification, causing all signatures to fail

How to avoid: Firebase Cloud Functions automatically parse the JSON body. Stripe's signature verification needs the raw body. Use req.rawBody which contains the unparsed request body.

Best practices

  • Always verify Stripe webhook signatures using stripe.webhooks.constructEvent() to prevent forged events
  • Store Stripe API keys in Cloud Secret Manager using defineSecret() — never in .env files or code
  • Use the Stripe Extension for standard subscription workflows to avoid writing boilerplate webhook code
  • Set Firestore security rules so users can only read their own customer and subscription documents
  • Handle both subscription creation and updates in the same case block to handle edge cases
  • Log all Stripe events with structured logging for debugging payment issues
  • Set maxInstances on your webhook function to prevent scaling issues during high-traffic events
  • Test webhooks locally using Stripe CLI: stripe listen --forward-to localhost:5001/project/region/stripeWebhook

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

Show me how to build a Firebase Cloud Function that handles Stripe webhooks. I need to verify the webhook signature, handle checkout.session.completed and subscription events, and sync data to Firestore. Use v2 Cloud Functions with defineSecret for the Stripe keys.

Firebase Prompt

Create a Firebase + Stripe integration using Cloud Functions v2. Build an onRequest webhook handler that verifies Stripe signatures using req.rawBody, stores Stripe keys with defineSecret, and syncs checkout.session.completed and subscription events to Firestore. Include security rules for the customer data.

Frequently asked questions

Should I use the Stripe Extension or build a custom webhook?

Use the extension for standard subscription and checkout workflows — it handles the common cases without code. Build a custom webhook if you need custom business logic, one-time payments with complex fulfillment, or integration with other services beyond Firestore.

Do I need the Blaze plan for Stripe integration?

Yes. Both the Stripe Extension and custom webhook Cloud Functions require the Blaze (pay-as-you-go) plan. Cloud Functions cannot be deployed on the free Spark plan.

How do I test Stripe webhooks locally?

Install the Stripe CLI and run: stripe listen --forward-to localhost:5001/your-project/us-central1/stripeWebhook. This forwards Stripe test events to your local Firebase Emulator. Use stripe trigger checkout.session.completed to simulate events.

Why are my webhook signatures always failing?

The most common cause is using req.body (parsed JSON) instead of req.rawBody (raw string) for signature verification. Stripe needs the exact bytes that were sent. Also verify you are using the correct webhook signing secret (not the API key).

How do I handle subscription renewals?

Listen for the invoice.payment_succeeded event for successful renewals and invoice.payment_failed for failed payments. The customer.subscription.updated event fires when the subscription status changes. If you need help architecting complex payment flows, RapidDev can build a production-ready Stripe integration for your Firebase app.

Can I use Stripe with Firebase Auth?

Yes. Create a Stripe customer when a Firebase user signs up, and store the Stripe customer ID in Firestore linked to the Firebase UID. The Stripe Extension does this automatically. For custom implementations, create the Stripe customer in an Auth onCreate trigger.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.