Skip to main content
Articles

Authentication + Billing: How Clerk and Stripe Work Together - Part 2

Author: Roy Anger
Published: (last updated )

How do Clerk and Stripe work together for authentication and billing? - Part 2

This is the second part of a two-part series on Clerk Billing. Part 1 covered the conceptual architecture and a B2C billing walkthrough. This part covers B2B billing, advanced webhooks, and a build-vs-buy comparison.

B2B example: organization plans, roles, and features

B2B billing charges the organization, and its members inherit access from the organization's plan. The component changes by one prop:

import { PricingTable } from '@clerk/nextjs'

export default function PricingPage() {
  return <PricingTable for="organization" />
}

With for="organization", the table shows the organization's plans rather than the individual user's, so the user needs an organization set as active before a subscription can attach to it. Let members pick one with <OrganizationSwitcher /> (or set it in code with setActive()). From then on, a member's has({ plan: '...' }) or has({ feature: '...' }) check resolves against the active organization's plan.

The <PricingTable /> itself is forgiving here, but the experimental billing buttons are stricter. Per Clerk's docs, <CheckoutButton for="organization"> and <SubscriptionDetailsButton for="organization"> throw if no organization is active, and both must be wrapped in <Show when="signed-in">. Set the active organization before you render them.

How plans and roles combine

This is the one place where the model is easy to get wrong, so here's how it actually works.

A plan grants features. A role grants permissions. They aren't two parallel lists of features that get merged. The connection between them is a gating rule: a permission check only passes if the feature in that permission's key is part of the organization's active plan.

Permission keys have the shape org:<feature>:<permission>. Per Clerk's B2B billing guide, "if you are checking a Custom Permission, it will only work if the Feature part of the Permission key (org:<feature>:<permission>) is a Feature included in the Organization's active Plan." So a member with the org:reports:manage permission can only use it if the organization's plan includes the reports feature. If it doesn't, the check returns false even though the member holds the permission, and the permission won't even appear on the session token.

The plain-language version: the plan decides which features the organization has, and the member's role decides what they can do with those features.

Gate by role, permission, plan, or feature

All four use the same has() check on the server. A permission check looks like this:

import { auth } from '@clerk/nextjs/server'

export default async function ManageReportsPage() {
  const { has } = await auth()

  if (!has({ permission: 'org:reports:manage' })) {
    return <h1>You need the Reports Manager permission to view this page.</h1>
  }

  return <h1>Organization reports</h1>
}

Swap the argument for { role: 'org:admin' } to gate by role, or { feature: 'reports' } to gate by plan feature. Clerk ships two default roles, org:admin and org:member, and you can define your own custom roles with their own permission sets. Clerk's own guidance is to prefer permission checks over role checks, and feature checks over plan checks, because they survive plan and role changes better.

Per-seat pricing and the B2B add-on

For organization plans, per-seat pricing combines a base fee with a per-seat fee. To borrow Clerk's worked example, a plan with "a $20 per month base fee, an $8 per month per-seat fee, 2 included seats, a limit of 10 seats" charges $20 for one seat, $28 for three seats, and $84 at the ten-seat limit.

Some advanced organization features sit behind the B2B Authentication add-on ($100/mo, or $85/mo billed annually, on top of your base plan): more than 20 seats or unlimited members per organization, custom roles and role sets, linking enterprise SSO connections to organizations, and verified domains. Billing itself isn't gated; it's included on every tier, including the free Hobby plan. Only those advanced organization features need the add-on.

Can one app do both B2C and B2B?

Yes. Clerk Billing supports combining per-user and per-organization billing in the same application. A has({ plan: '...' }) check resolves against the user's personal plan when no organization is active, and against the active organization's plan when one is.

This guide keeps the two as separate examples on purpose. Most apps lean one way or the other, and the mechanics are distinct enough that two clean walkthroughs teach better than one combined example. If you do need both, the pieces compose without any special mode.

Advanced: reacting to billing events with webhooks

For entitlement checks you don't need webhooks at all; the session token already carries the answer. Webhooks are for out-of-band side effects, like sending your own emails, provisioning external resources, or syncing to a downstream system.

Clerk delivers billing events through Svix and retries on any non-2xx response. The event names are camelCase. The ones worth listening for:

EventTypical use
subscriptionItem.activeProvision access when a subscription becomes active
subscriptionItem.pastDueTrigger your own dunning or reminder messaging
subscriptionItem.canceledSchedule a downgrade
subscriptionItem.endedRevoke access tied to the subscription
subscriptionItem.freeTrialEndingSend a trial-ending nudge (fires 3 days before)
paymentAttempt.updatedRecord a receipt or a failed-payment note

Verify every webhook with verifyWebhook from @clerk/nextjs/webhooks. The handler shape is the same one Clerk uses for all webhooks; only the event names differ.

import { verifyWebhook } from '@clerk/nextjs/webhooks'
import { NextRequest } from 'next/server'

export async function POST(req: NextRequest) {
  try {
    const evt = await verifyWebhook(req)

    switch (evt.type) {
      case 'subscriptionItem.active':
        // Provision access for the new subscription
        break
      case 'subscriptionItem.canceled':
        // Schedule a downgrade at period end
        break
      case 'subscriptionItem.freeTrialEnding':
        // Send your own trial-ending reminder
        break
      default:
        break
    }

    return new Response('Webhook received', { status: 200 })
  } catch (err) {
    console.error('Error verifying webhook:', err)
    return new Response('Error verifying webhook', { status: 400 })
  }
}

Add the endpoint under Webhooks in the Dashboard. Because clerkMiddleware() leaves routes public by default, the webhook route works out of the box; if you've switched to a protect-everything pattern, make sure to exclude it. Svix sends an svix-id with each delivery, which you can use as an idempotency key so retries don't double-process.

To test the endpoint locally, expose your dev server to the internet with a tunnel like ngrok (Clerk also documents localtunnel, Cloudflare Tunnel, and Pinggy), then register that forwarding URL as the endpoint. From the endpoint's Testing tab in the Dashboard you can send a sample event and confirm it reaches your handler. Billing events ride the same Svix pipeline as every other Clerk webhook, so the local-testing flow is identical.

A useful way to think about which tool to reach for:

  • Use the components and has() / <Show> for in-app gating.
  • Use the backend billing API (getUserBillingSubscription, getOrganizationBillingSubscription) for an authoritative server-side read of a subscription.
  • Use webhooks for side effects that happen outside a request.

If you need a fully custom checkout (your own form rather than the prebuilt drawer), it's possible through the experimental useCheckout() and usePaymentElement() hooks together with the <PaymentElement /> component. That's beyond a components-first setup; see Clerk's custom flows docs when you get there, and keep your SDK versions pinned because those APIs are experimental.

Build vs. buy: Clerk + Stripe vs. rolling your own

Stripe is excellent, and Clerk Billing runs on top of it. The real question is how you connect auth, payments, and entitlements: wire Stripe to your app and database yourself, or let Clerk own that seam. That's the comparison this section makes.

Developer time

With components, you can have a working paywall in minutes to hours: configure plans in the Dashboard, drop in <PricingTable />, add a has() check. Building the equivalent yourself, including the subscription model, the checkout flow, the customer portal, and the webhook layer, is typically a multi-week project before you handle the first edge case.

There's no credible industry-wide number for "hours saved," so take the time estimate as directional. The more concrete argument is the list of edge cases below, which are real regardless of who builds them.

The edge cases that are hard to get right

These are documented realities of subscription billing, straight from Stripe's own documentation, and they're the same whether you build on Stripe directly or use Clerk:

  • Subscription state drift. Stripe is the source of truth, and your app has to mirror its state through webhooks, even when your app never calls Stripe directly. Keeping that mirror correct is ongoing work.
  • Webhook delivery. Stripe webhooks are delivered at least once and can arrive out of order, so handlers need signature verification, idempotency, and fast acknowledgement with async processing.
  • Failed payments and dunning. A declined renewal moves a subscription through states like past_due and unpaid, and recovering revenue means building a retry and dunning flow.
  • Proration. Mid-cycle upgrades and downgrades require proration, which Stripe's docs describe as one of the most complex parts of subscriptions.
  • Strong Customer Authentication. Off-session renewals can hit a requires_action step under SCA, which your renewal logic has to handle.

Even Stripe Entitlements, which is the closest building block to what Clerk provides, still leaves you to catch a webhook, persist the entitlement state, and enforce it on every check. Clerk's contribution is that last mile: the entitlement is already on the session token, and you check it in-process with has().

Ongoing maintenance

A homegrown system isn't done when it ships. You own API-version upgrades, support for new payment methods, the webhook endpoint's uptime, and every entitlement check scattered through the codebase. Folding billing into the auth SDK shrinks that surface to plan and feature definitions in a dashboard.

Side by side

ConcernRoll your own (raw Stripe)Clerk + Stripe
Subscription state syncYou mirror Stripe into your database via webhooksReflected onto the user and session token automatically
Entitlement checkQuery your database or Stripe on each checkhas({ plan }) / has({ feature }) reads the token, no database call
Customer-facing UIBuild pricing, checkout, and manage-subscription pagesPrebuilt <PricingTable /> and billing components
Webhook idempotency and retriesYou implement verification, idempotency, orderingOptional; gating works without writing any
Failed-payment handlingYou build the dunning state machineSubscription goes past due; customer returns to the free plan on non-payment
Time to first paywallDays to weeksMinutes to hours
Tax / VATYour responsibility (for example, Stripe Tax)Not handled by Clerk today (use Stripe Tax)
3D Secure / SCASupported through StripeNot supported by Clerk Billing today
Multi-currencySupported through StripeUSD-only today

The bottom three rows are the honest cases where doing it yourself (or going straight to Stripe) gives you something Clerk Billing doesn't have yet. They matter, and they're covered in Limitations.

How Clerk compares to other authentication providers for billing

Here's where things stand as of mid-2026, kept deliberately general.

Most authentication providers have no native billing at all. You bring your own Stripe integration and your own database, exactly the build described above. A few offer something in between: an entitlements bridge that reads from a Stripe account you still manage, or a self-hosted plugin you run and own. In those cases you still own the billing relationship, the payment flow, and the sync layer.

What Clerk does differently is unify the whole thing behind the auth SDK. One SDK, one dashboard, and entitlements that live on the same session token you already verify for auth, with prebuilt customer-facing UI and no sync layer to maintain. The differentiator here is depth and unification. The broader idea of auth providers adding billing is an emerging category, and at least one other provider already offers a Stripe-backed billing integration, so the honest claim is about how tightly Clerk fuses billing with auth and how much of the surface (components, per-seat pricing, custom plans, non-gated trials and annual billing, add-ons) is built in.

The practical takeaway if you're choosing an auth provider today: if you know you'll need billing, the integration cost with Clerk is close to zero, versus a multi-week build almost everywhere else. That's worth weighing now even if billing is a later milestone.

Limitations and gotchas to know up front

Covered honestly, because due diligence will surface these anyway, and disclosing them up front is the difference between a guide that's trusted and one that isn't.

LimitationDetail and workaround
BetaBilling is in Beta and its APIs are experimental. Pin your @clerk/nextjs and clerk-js versions while you build.
0.7% feeClerk Billing adds 0.7% per transaction on top of Stripe's standard fees (2.9% + $0.30 in the US). It's the same total cost as using Stripe Billing directly; there's no Clerk markup over Stripe Billing.
No tax / VATClerk Billing doesn't currently support tax or VAT (it's on the roadmap). Use Stripe Tax if you need it now.
No refunds through ClerkIssue refunds in your Stripe account directly. Refunds done in Stripe won't be reflected in Clerk's income and MRR figures.
No 3D Secure / SCAIf a payment method requires the extra confirmation step, the customer is asked to use a different method. This matters for EU-facing apps, where PSD2 mandates SCA for many consumer card payments.
USD-onlyBilling currency is USD only today, regardless of your Stripe account's local currency. Multi-currency is on the roadmap.
Six excluded countriesNot supported in Brazil, India, Malaysia, Mexico, Singapore, and Thailand, due to payment-processing restrictions. Availability elsewhere depends on Stripe.
Advanced org featuresMore than 20 seats / unlimited members, custom roles and role sets, enterprise SSO linked to orgs, and verified domains need the B2B Authentication add-on ($100/mo, $85/mo annually). Billing itself needs no add-on.
Default plan is fixedYou can't yet change which plan is the default (the free plan); you can rename it and change its visibility.

One more behavior that comes up often: what happens when a member is removed from an organization. The subscription belonged to the organization, not the member, so the features the organization's plan granted end when their membership ends. The user falls back to their own personal plan, which is the free default plan if they don't hold a paid personal subscription. They don't keep org-granted access, and they don't inherit the org's bill.

Getting started

No hard sell here, just the paths forward.

If you're already on Clerk, adding billing is mostly the Dashboard work in Step 1 plus a <PricingTable /> and a has() check. If you're evaluating auth providers and you know billing is coming, that short path is the thing to weigh.

Frequently asked questions

Can one app support both B2C and B2B billing? Yes. Clerk Billing allows you to combine per-user and per-organization billing in the same application. The has() check automatically resolves against the user's personal plan when no organization is active, and against the active organization's plan when one is.

Do I need to handle webhooks for entitlement checks? No. The session token already carries the user's or organization's active plan and features. You only need webhooks for out-of-band side effects, like provisioning external resources or sending your own emails.

Does Clerk Billing support per-seat pricing? Yes, for organization plans. You can charge a base fee plus a per-seat fee, and Clerk automatically adjusts the seat count to match the organization's membership at the start of each billing period.

Do I need a paid Clerk plan to use Billing? No. Billing is included on every Clerk plan, including the free Hobby tier; you only pay Clerk's 0.7% per-transaction fee on top of Stripe's fees. A few advanced organization features (more than 20 seats or unlimited members, custom roles and role sets, enterprise SSO linked to organizations, and verified domains) need the paid B2B Authentication add-on, but billing itself doesn't.

What happens when a customer's payment fails? The subscription goes past due and Clerk sends a subscriptionItem.pastDue webhook you can use for your own dunning or reminders. If it stays unpaid, the customer falls back to your free default plan, so their entitlements drop to the free tier automatically.

Does Clerk handle sales tax and VAT? Not today; both are on the roadmap. Because Stripe processes the payments, you can pair Clerk Billing with a Stripe-integrated tax tool like Stripe Tax to stay compliant in the meantime.

Does Clerk Billing support 3D Secure / SCA? Not today. If a payment method requires the extra 3D Secure confirmation step, the customer is asked to use a different method. That matters for EU-facing apps, where PSD2 mandates SCA for many consumer card payments.

Which countries and currencies does Clerk Billing support? Billing is USD-only today, regardless of your Stripe account's local currency (multi-currency is on the roadmap). It works in most countries Stripe supports, with six current exceptions: Brazil, India, Malaysia, Mexico, Singapore, and Thailand.

This concludes our two-part series on Clerk Billing. If you missed it, check out Part 1 for the conceptual architecture and a B2C billing walkthrough.

In this series

  1. Authentication + Billing: How Clerk and Stripe Work Together
  2. Authentication + Billing: How Clerk and Stripe Work Together - Part 2 (you are here)