# How to Connect Firebase to Stripe for Payments

- Tool: Firebase
- Difficulty: Intermediate
- Time required: 20-30 min
- Compatibility: Firebase Blaze plan, Cloud Functions v2, Stripe API, firebase-functions 4.x+
- Last updated: March 2026

## 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.

## Before you start

- 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.

```
# Install via Firebase CLI
firebase ext:install stripe/firestore-stripe-payments --project=your-project-id

# The CLI prompts for:
# - Stripe API key (from Stripe Dashboard > Developers > API keys)
# - Stripe webhook signing secret
# - Firestore collection names (default: customers, products, subscriptions)
# - 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.

```
// Stripe webhook URL format (from Firebase Console > Functions):
// https://us-central1-your-project.cloudfunctions.net/ext-firestore-stripe-payments-handleWebhookEvents

// Events to subscribe to in Stripe Dashboard:
// - checkout.session.completed
// - customer.subscription.created
// - customer.subscription.updated
// - customer.subscription.deleted
// - invoice.payment_succeeded
// - 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.

```
import { db } from "@/lib/firebase";
import { auth } from "@/lib/firebase";
import {
  collection, addDoc, onSnapshot
} from "firebase/firestore";

async function createCheckoutSession(priceId: string) {
  const user = auth.currentUser;
  if (!user) throw new Error("Must be logged in");

  // Create a checkout session document
  const checkoutRef = await addDoc(
    collection(db, "customers", user.uid, "checkout_sessions"),
    {
      price: priceId,           // Stripe Price ID from your products
      success_url: window.location.origin + "/success",
      cancel_url: window.location.origin + "/pricing",
    }
  );

  // Listen for the Stripe session URL
  onSnapshot(checkoutRef, (snap) => {
    const data = snap.data();
    if (data?.url) {
      window.location.assign(data.url); // Redirect to Stripe Checkout
    }
    if (data?.error) {
      console.error("Checkout error:", data.error.message);
    }
  });
}
```

**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().

```
import { onRequest } from "firebase-functions/v2/https";
import { defineSecret } from "firebase-functions/params";
import { getFirestore } from "firebase-admin/firestore";
import * as admin from "firebase-admin";
import Stripe from "stripe";

admin.initializeApp();
const db = getFirestore();

const stripeSecretKey = defineSecret("STRIPE_SECRET_KEY");
const webhookSecret = defineSecret("STRIPE_WEBHOOK_SECRET");

export const stripeWebhook = onRequest(
  {
    secrets: [stripeSecretKey, webhookSecret],
    cors: false, // Stripe does not use CORS
  },
  async (req, res) => {
    const stripe = new Stripe(stripeSecretKey.value());

    // Verify webhook signature
    let event: Stripe.Event;
    try {
      event = stripe.webhooks.constructEvent(
        req.rawBody,
        req.headers["stripe-signature"] as string,
        webhookSecret.value()
      );
    } catch (err) {
      res.status(400).send(`Webhook signature verification failed`);
      return;
    }

    // Handle events
    switch (event.type) {
      case "checkout.session.completed": {
        const session = event.data.object as Stripe.Checkout.Session;
        await db.doc(`orders/${session.id}`).set({
          customerId: session.customer,
          status: "paid",
          amount: session.amount_total,
          createdAt: admin.firestore.FieldValue.serverTimestamp(),
        });
        break;
      }
      case "customer.subscription.updated": {
        const sub = event.data.object as Stripe.Subscription;
        await db.doc(`subscriptions/${sub.id}`).update({
          status: sub.status,
          currentPeriodEnd: new Date(sub.current_period_end * 1000),
        });
        break;
      }
    }

    res.json({ received: true });
  }
);
```

**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.

```
import { db, auth } from "@/lib/firebase";
import {
  collection, query, where, getDocs
} from "firebase/firestore";

async function checkSubscriptionStatus(): Promise<boolean> {
  const user = auth.currentUser;
  if (!user) return false;

  // With Stripe Extension:
  const subsRef = collection(
    db, "customers", user.uid, "subscriptions"
  );
  const q = query(
    subsRef,
    where("status", "in", ["active", "trialing"])
  );

  const snapshot = await getDocs(q);
  return !snapshot.empty;
}

// Usage:
const isPremium = await checkSubscriptionStatus();
if (isPremium) {
  // Show premium features
} else {
  // Show upgrade prompt
}
```

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

## Complete code example

File: `functions/src/stripe-webhook.ts`

```typescript
import { onRequest } from "firebase-functions/v2/https";
import { defineSecret } from "firebase-functions/params";
import { getFirestore } from "firebase-admin/firestore";
import * as admin from "firebase-admin";
import { logger } from "firebase-functions";
import Stripe from "stripe";

admin.initializeApp();
const db = getFirestore();

const stripeSecretKey = defineSecret("STRIPE_SECRET_KEY");
const webhookSecret = defineSecret("STRIPE_WEBHOOK_SECRET");

export const stripeWebhook = onRequest(
  {
    secrets: [stripeSecretKey, webhookSecret],
    cors: false,
    maxInstances: 10,
  },
  async (req, res) => {
    const stripe = new Stripe(stripeSecretKey.value());

    let event: Stripe.Event;
    try {
      event = stripe.webhooks.constructEvent(
        req.rawBody,
        req.headers["stripe-signature"] as string,
        webhookSecret.value()
      );
    } catch (err) {
      logger.error("Webhook signature failed:", err);
      res.status(400).send("Signature verification failed");
      return;
    }

    logger.info(`Stripe event: ${event.type}`);

    switch (event.type) {
      case "checkout.session.completed": {
        const session = event.data
          .object as Stripe.Checkout.Session;
        await db.doc(`orders/${session.id}`).set({
          stripeCustomerId: session.customer,
          status: "paid",
          amount: session.amount_total,
          currency: session.currency,
          createdAt:
            admin.firestore.FieldValue.serverTimestamp(),
        });
        break;
      }
      case "customer.subscription.created":
      case "customer.subscription.updated": {
        const sub = event.data
          .object as Stripe.Subscription;
        await db.doc(`subscriptions/${sub.id}`).set(
          {
            stripeCustomerId: sub.customer,
            status: sub.status,
            priceId: sub.items.data[0]?.price.id,
            currentPeriodEnd: new Date(
              sub.current_period_end * 1000
            ),
            updatedAt:
              admin.firestore.FieldValue.serverTimestamp(),
          },
          { merge: true }
        );
        break;
      }
      case "customer.subscription.deleted": {
        const sub = event.data
          .object as Stripe.Subscription;
        await db.doc(`subscriptions/${sub.id}`).update({
          status: "canceled",
          canceledAt:
            admin.firestore.FieldValue.serverTimestamp(),
        });
        break;
      }
    }

    res.json({ received: true });
  }
);
```

## Common mistakes

- **Not verifying the Stripe webhook signature, allowing anyone to send fake payment events to your endpoint** — undefined Fix: Always call stripe.webhooks.constructEvent() with the raw request body, stripe-signature header, and your webhook signing secret. Return 400 if verification fails.
- **Storing the Stripe secret key in a .env file or hardcoding it in function code** — undefined Fix: 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.
- **Forgetting to add the Cloud Function URL as a webhook endpoint in the Stripe Dashboard** — undefined Fix: 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.
- **Using req.body instead of req.rawBody for webhook signature verification, causing all signatures to fail** — undefined Fix: 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

## 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.

---

Source: https://www.rapidevelopers.com/firebase-tutorial/how-to-connect-firebase-to-stripe
© RapidDev — https://www.rapidevelopers.com/firebase-tutorial/how-to-connect-firebase-to-stripe
