DaaS / Products / Branded SaaS Auth-to-Payment Onboarding Flow

Branded SaaS Auth-to-Payment Onboarding Flow

A developer builds a SaaS application where users sign up through a custom Clerk authentication flow and then complete subscription payment via Stripe, with both UIs styled to match the product's brand design system for a seamless onboarding experience.

Products involved

Scenario

Use this workflow when building a SaaS product that requires a fully branded onboarding experience, where users authenticate via a custom Clerk flow and immediately transition to a styled Stripe subscription checkout without breaking your design system.

Integration steps

  1. Route to Custom Auth: Initialize Clerk with @clerk/nextjs. Create a dedicated /auth/sign-up route and mount <SignUp routing="path" path="/auth/sign-up" /> to bypass default hosted pages.
  2. Apply Clerk Appearance: Pass the appearance prop to Clerk components to enforce brand tokens: appearance={{ variables: { colorPrimary: '#0F172A', fontFamily: 'Inter, sans-serif' }, elements: { formButtonPrimary: 'bg-brand-600 hover:bg-brand-700' } }}.
  3. Provision Stripe Customer: On successful Clerk sign-up, trigger a server action: const customer = await stripe.customers.create({ email: user.emailAddresses[0].emailAddress, metadata: { clerk_user_id: user.id } }).
  4. Initialize Stripe Elements: Generate a Payment Intent and mount Stripe Elements using the Appearance API v3: const elements = stripe.elements({ clientSecret: intent.client_secret, appearance: { theme: 'stripe', variables: { colorPrimary: '#0F172A', borderRadius: '8px' } } }).
  5. Render & Confirm Payment: Mount <PaymentElement /> and handle submission: await stripe.confirmPayment({ elements, confirmParams: { return_url: ${process.env.NEXT_PUBLIC_URL}/dashboard } }).
  6. Sync Subscription State: Configure a /webhooks/stripe endpoint. Verify with stripe.webhooks.constructPayload(), then update Clerk: await clerkClient.users.updateUserMetadata(clerkId, { publicMetadata: { stripeSubscriptionId: event.data.object.id } }).

Architecture

Clerk owns identity, session management, and user metadata. Stripe owns payment collection, subscription lifecycle, and billing events. The backend bridges the two: it maps Clerk user.id to a Stripe customer.id, generates a client_secret for the payment form, and consumes Stripe webhooks to write subscription status back into Clerk’s publicMetadata. The frontend gates routes by reading Clerk session metadata.

Prerequisites

Common pitfalls

Typical questions