Guide · 2026-03-13

How to Sync Stripe Subscriptions with Supabase

How to keep Stripe subscriptions and Supabase in sync so access, billing state, and plan changes actually reflect in your app.

Quick Answer

The reliable way to sync Stripe subscriptions with Supabase is:

  • store Stripe customer and subscription IDs in your database
  • trust the webhook, not the redirect
  • handle created, updated, deleted, and failed-payment events
  • update access state from Stripe events, not from frontend assumptions
  • If checkout succeeds but the app does not unlock, the problem is almost always your webhook flow or database mapping.

    The Core Rule

    Stripe is the source of truth for billing.

    Your database is the source of truth for product access inside the app.

    The webhook is the bridge between them.

    If that bridge is weak, users pay and still see the wrong plan.

    What to Store in Supabase

    At minimum, your subscriptions table should track:

  • user_id
  • stripe_customer_id
  • stripe_subscription_id
  • stripe_price_id
  • status
  • current_period_end
  • created_at
  • updated_at
  • That is enough to answer the important app questions:

  • is this user active?
  • what plan are they on?
  • when does it renew or end?
  • A Practical Table Shape

    sql
    create table if not exists subscriptions (
      id uuid primary key default gen_random_uuid(),
      user_id uuid not null references auth.users(id),
      stripe_customer_id text unique,
      stripe_subscription_id text unique,
      stripe_price_id text,
      status text not null,
      current_period_end timestamptz,
      created_at timestamptz not null default now(),
      updated_at timestamptz not null default now()
    );

    Then add RLS so users can read only their own row.

    The Frontend Mistake to Avoid

    Do not unlock access just because the browser returned from Stripe.

    The success page is not proof.

    The only trustworthy moment is when Stripe sends the webhook and your backend updates Supabase successfully.

    Which Events You Actually Need

    For most SaaS apps, handle these:

  • checkout.session.completed
  • customer.subscription.created
  • customer.subscription.updated
  • customer.subscription.deleted
  • invoice.payment_failed
  • That covers the real lifecycle:

  • checkout success
  • upgrades or plan changes
  • cancellations
  • failed recurring payments
  • Minimal Webhook Logic

    Your webhook should:

  • verify the Stripe signature
  • parse the event
  • find the matching user or customer record
  • upsert the subscription row
  • return 200 quickly
  • The important part is not cleverness. It is consistency.

    Example Handler Shape

    ts
    switch (event.type) {
      case "checkout.session.completed":
        // store customer/subscription linkage if needed
        break;
      case "customer.subscription.created":
      case "customer.subscription.updated":
        // upsert status, price, period end
        break;
      case "customer.subscription.deleted":
        // mark subscription inactive/cancelled
        break;
      case "invoice.payment_failed":
        // downgrade access or flag billing issue
        break;
    }

    How to Map Stripe Back to the User

    Best options:

  • metadata.user_id
  • stored stripe_customer_id
  • customer email as fallback only
  • Email works, but it is weaker than a deliberate ID link.

    The cleanest pattern is to attach your internal user_id in Stripe metadata when creating the checkout session.

    The Most Common Sync Failures

    1. Payment succeeded, access did not change

    Usually means:

  • webhook never fired
  • webhook failed
  • webhook did not update the right row
  • 2. Upgrade happened, old plan still shows

    Usually means:

  • app is reading stale cached data
  • subscription update event is ignored
  • stripe_price_id never gets updated
  • 3. Cancellation in Stripe does nothing in the app

    Usually means:

  • customer.subscription.deleted is not handled
  • 4. Failed payment does not downgrade access

    Usually means:

  • invoice.payment_failed is ignored
  • The Fastest Way to Debug

    Check in this order:

  • Stripe event delivery logs
  • webhook response code
  • backend function logs
  • Supabase row update
  • frontend read path
  • If you start in the UI first, you usually lose time.

    Local Testing

    Use Stripe CLI for local webhook testing:

    bash
    stripe listen --forward-to localhost:3000/api/webhooks/stripe
    stripe trigger customer.subscription.updated

    That is much faster than guessing in production.

    What the App Should Check

    Your app UI should not guess from "has user paid once?"

    It should read:

  • status
  • stripe_price_id
  • current_period_end
  • and then decide what the user can access.

    That keeps pricing, billing, and permissions aligned.

    Good Access Rules

    Examples:

  • active or trialing → allow access
  • past_due → maybe limited access or billing warning
  • canceled or unpaid → remove premium access
  • The exact rule is your product choice, but it should be explicit.

    Best Prompt for Cursor or Lovable

    text
    Audit my Stripe + Supabase subscription flow.
    
    Check for:
    - where Stripe customer IDs are stored
    - whether webhook signature verification is correct
    - whether subscription lifecycle events are all handled
    - whether Supabase access state updates after checkout, renewals, failed payments, and cancellations
    - whether the frontend reads subscription state from the database or incorrectly trusts the success redirect
    
    Return the highest-risk issues first.

    Final Checklist

  • Stripe customer IDs are stored
  • Subscription IDs are stored
  • Webhook signature verification uses raw request body
  • Subscription created/updated/deleted events are handled
  • Failed payment events are handled
  • Supabase rows update after webhook delivery
  • Frontend access state reads from the database, not the redirect
  • Related Guides

  • How to Add Stripe Payments to Your Lovable App
  • How to Migrate Off Lovable to Your Own Stack
  • Sweden/EU Launch Compliance Checklist
  • From Lovable to Live
  • Recommended Stack

    Services we recommend for deploying your vibe coded app

    How to Sync Stripe Subscriptions with Supabase | Gptsters