惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
N
News and Events Feed by Topic
D
DataBreaches.Net
MongoDB | Blog
MongoDB | Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Engineering at Meta
Engineering at Meta
T
Tailwind CSS Blog
博客园_首页
Microsoft Azure Blog
Microsoft Azure Blog
Y
Y Combinator Blog
博客园 - Franky
Hugging Face - Blog
Hugging Face - Blog
月光博客
月光博客
A
About on SuperTechFans
I
InfoQ
S
Securelist
Last Week in AI
Last Week in AI
S
Schneier on Security
C
CXSECURITY Database RSS Feed - CXSecurity.com
Hacker News: Ask HN
Hacker News: Ask HN
Schneier on Security
Schneier on Security
Know Your Adversary
Know Your Adversary
腾讯CDC
大猫的无限游戏
大猫的无限游戏
S
Security @ Cisco Blogs
博客园 - 三生石上(FineUI控件)
Simon Willison's Weblog
Simon Willison's Weblog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Tor Project blog
美团技术团队
aimingoo的专栏
aimingoo的专栏
G
Google Developers Blog
罗磊的独立博客
Vercel News
Vercel News
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
The Cloudflare Blog
S
Secure Thoughts
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Latest news
Latest news
Recent Announcements
Recent Announcements
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
L
LINUX DO - 热门话题
Security Latest
Security Latest
TaoSecurity Blog
TaoSecurity Blog
Cyberwarzone
Cyberwarzone
有赞技术团队
有赞技术团队

Clerk Blog

Going to production with Clerk Deploy Clerk Init: The fastest way to start a new project Introducing Clerk CLI Middleware-based route protection bypass Postmortem: Clerk System Outage (March 10, 2026) Clerk for the AI era Add API Key support to your SaaS in minutes Postmortem: Clerk System Outage (February 19, 2026) Using Clerk in a React Native app Postmortem: DNS Provider Outage (February 10, 2026) How do I implement passkeys in Next.js? Clerk ranked #4 fastest-growing software vendor on Ramp’s December 2025 list How do I handle JWT verification in Next.js? Committing to Agent Identity: Clerk raises $50m Series C from Menlo and Anthropic’s Anthology Fund What is the best way to handle authentication in Next.js App Router? Postmortem: Database Incident (September 14–18, 2025) How do I add authentication to a Next.js app? Introducing Free Trials in Clerk Billing Postmortem: August 28, 2025 - elevated API latency and errors Introducing Mosaic: Bring Your Brand to Every Authentication Flow Multi-tenant authentication: What you need to know (and how Clerk helps) What are the risks and challenges of multi-tenancy? Resilience in Practice: Regional Failover at Clerk Build a Cross-Platform B2B App with Clerk, Expo, and Supabase Highlights from the MiduDev/Clerk Hackathon Add multi-tenancy to an app built with Clerk, Lovable, and Supabase How to build an AI coding rules app with Clerk, Lovable, and Supabase How to Build Multi-Tenant Authentication with Clerk Choosing the right SaaS architecture: Multi-Tenant vs. Single-Tenant Postmortem: June 26, 2025 service outage How to Design a Multi-Tenant SaaS Architecture What is multi-tenancy and why it matters for B2B SaaS How OAuth Works Synchronize user data from Clerk to Supabase Add subscriptions to your SaaS with Clerk Billing Getting started with Clerk Billing Multi-tenant analytics with Tinybird and Clerk How Huntr Migrated 250K Users to Clerk: A Scalable Auth Solution for Startups How to take Clerk to Production How to take your Clerk application to production A practical guide to testing Clerk Next.js applications Implementing multi-tenancy into a Supabase app with Clerk How Clerk integrates with a Next.js application using Supabase How Clerk integrates with Supabase Build a blog with tRPC, Prisma, Next.js and Clerk How to enrich PostHog events with Clerk user data How to build a secure project management platform with Next.js, Clerk, and Neon Validate your SaaS idea while building an audience Postmortem: February 6, 2025 service outage Implement Role-Based Access Control in Next.js 15 Build a Next.js sign-up form with React Hook Form Build a Next.js login page template How to implement Google authentication in Next.js 15 What is middleware in Next.js? How to customize Next.js metadata How to set environment variables in Node.js Building a React Login Page Template How to implement per-user OAuth scopes with Clerk Using Clerk SSO to access Google Calendar and other service data Streamline enterprise customer onboarding with SAML and Clerk Clerk launches EASIE SSO and eliminates SSO fees How to secure Liveblocks Rooms with Clerk in Next.js Securing Node.js Express APIs with Clerk and React Combining the benefits of session tokens and JWTs Build a task manager with Next.js, Supabase, and Clerk Comparing Clerk Webhooks vs Backend API Automate Neon schema changes with Drizzle and GitHub Actions A guide to reading authenticated user data from Clerk Role based access control with Clerk Organizations Mitigating OAuth’s recently discovered Open Response Type vulnerability Build a team-based task manager with Next.js, Neon, and Clerk Building a Hybrid Sign-Up/Subscribe Form with Stripe Elements Welcoming Colin from Zod as our inaugural Open Source Fellow Build a modern authenticated chat application with Next.js, Ably, and Clerk Build a waitlist with Clerk user metadata How to use Clerk with PostHog Identify in Next.js How to secure API Gateway using JWT and Lambda Authorizers with Clerk What are passkeys and how do they work? Comparing Authentication in React.js vs. Next.js How to Add an Onboarding Flow for your Application with Clerk Create Your Own Custom User Menu with Radix - Part 2 Introducing Webhook Workflows with Inngest & Svix Clerk raises $30M Series B from CRV and Stripe Clerk in 2023: A Year in Review Build a Movie Emoji Quiz App with Remix, Fauna, and Clerk Ultimate Guide to Magic Link Authentication Create Your Own Custom User Menu with Radix Introducing has(), protect(), and <Protect> Updated Pricing: 10,000 MAUs Free, and a new “Pro Plan” Next.js Authentication with Clerk: Streamlined SSR Handling Clerk Webhooks: Data Sync with Convex Exploring Clerk Metadata with Stripe Webhooks The Ultimate Guide to Next.js Authentication Empower Your Support Team With User Impersonation Clerk Webhooks: Getting Started A Complete Guide to Session Management in Next.js The Advanced Guide to Passwordless Authentication in Next.js How We Roll – Chapter 10: Roundup How We Roll – Chapter 9: Infrastructure Password-Based Authentication in Next.js
Per-user B2B monetization with Stripe and Clerk Organizations
Brian Morrison II · 2024-08-02 · via Clerk Blog

Businesses tend to spend more money on software compared to individual consumers.

One of the most popular monetization models for B2B applications is the Per-User model, where a business purchases one “seat” for each user who will be using the application. Per-user pricing is a great way for application developers to generate income. The model is relatively straightforward, provides predictable pricing for finance departments, and allows for a steady stream of income that scales with the use of your application

In this article, you'll learn how Clerk Organizations and Stripe can be configured to implement per-user monetization into a web application.

This guide focuses on implementation details. For a comprehensive overview of B2B SaaS architecture and features, learn more about our B2B SaaS solution.

Project Overview

This article will use an open-source application called Team Task that is preconfigured with the functionality described below. All of the critical parts of the code that enable per-user licensing will be thoroughly explained, however, you are welcome to dive right into the code hosted in GitHub.

Everything is built with self-service in mind, so users will be able to do the following without assistance from you:

  • Create organizations and invite users
  • Add and manage licenses using Stripe
  • Assign and remove licenses from individual users

Using the pre-built Clerk components, users will be able to create organizations and invite users.

Once the organization is created, they will be prompted to purchase licenses via Stripe. Once purchased, administrators can toggle users to gain full use of the application.

Finally, administrators will also be able to easily manage their Stripe subscription with the click of a button.

Creating organizations

Clerk Organizations allows you to easily add multi-tenancy into an application by letting users create organizations and invite users into them using the OrganizationSwitcher component.

When a user creates an organization, they'll immediately be asked if there are any users they want to invite into the organization by simply providing a list of email addresses. Behind the scenes, our system will also check to see if the Clerk application has any endpoints that are configured to receive a webhook when an organization is created.

Webhooks are a way for one service to inform another when an event occurs. The event, in this case, was that an organization was created. Team Task contains a route handler at /api/clerk-hooks that is configured to accept the following payload that Clerk will send when an organization is created:

To automate the process of creating Stripe customer records based on organizations in the application, we can accept the webhook message, create a Stripe customer, and create a record in a Neon table to associate the Clerk org_id to the Stripe customer_id.

src/app/api/clerk-hooks/route.ts

This table will also track the number of licenses an organization has purchased, using a default value of 0 when the record is created.

A sample of data from the orgs table in Neon

Process overview

  1. A user starts the process by creating an organization in Clerk.
  2. Clerk's backend will asynchronously send a message to a route handler informing the application that a new organization was created.
  3. The application will create a customer in Stripe.
  4. Finally, the application will insert a new row into a Neon database to associate the Clerk Organization with the Stripe Customer, along with a default license count of 0.

The process diagram walking through the following steps: creating an organization in Clerk, a webhook being sent from Clerk to the application, the application creating a customer record in Stripe, the application storing the values in a Neon database.

Initial license purchase

Once the organization is created and users are invited, we'll redirect the current user to a page that lets them purchase licenses for the organization.

The OrganizationSwitcher uses the afterCreateOrganizationUrl prop to automatically forward the user to the /licensing page.

src/components/navbar.tsx

The licensing page is used for both the initial license purchase as well as managing and allocating licesnes over time. When the page is loading, it queries the license_count value in the orgs table for that organization to determine how to render the page.

src/app/licensing/page.tsx

The PurchaseLicensesCard component displays an input for the user to select how many licenses are required. Selecting the "Purchase via Stripe" button will use that value to create a Stripe Checkout Session using a server action.

Creating a Checkout Session requires the customer ID, a product ID that represents the per-user rate, purchase quantity, and redirect URLs. The session object returned from Stripe will contain the url that the user should be sent to for completing the transaction.

src/app/licensing/actions.ts

Since the PurchaseLicensesCard is a client component, we can redirect them using window.location.href:

src/app/licensing/PurchaseLicensesCard.tsx

The user will then be prompted for payment info to complete the transaction.

The Stripe checkout page.

After payment, they'll be redirected back to /licensing, where a list of users will be displayed to allocate licenses to.

Stripe offers webhooks for a wide array of events that occur on their end as well. The customer.subscription.created webhooks can be used to update the license_count value of an organization in the Neon database to match the value that was purchased:

src/app/api/stripe-hooks/route.ts

Process overview

  1. The user provides the number of licenses to purchase, and the application requests a Checkout Session URL from Stripe.
  2. The user is sent to Stripe to complete the transaction.
  3. Stripe will redirect the user back to the application URL while simultaneously sending an asynchronous webhook message to a route handler of the application.
  4. The application will update the license_count value for that organization in the Neon database.

A process diagram showing the following steps: the application requesting creating a checkout session from Stripe, the user being redirected for checkout, stripe redirecting back to the application on purchase as well as sending a webhook message to the application informing it of the number of licenses purchased, finally the application setting the value in the Neon database.

Licensing users

Now that licenses have been purchased, they need to be assigned to users.

As mentioned in the previous section, the /licensing page will automatically be updated to render a list of users who are members of an organization by querying the license_count value on load.

Since individual users can have access to multiple organizations within a single Clerk application, we use the concept of a “membership” to associate a user to an organization, modeling what is effectively a “many to many” relationship:

A crowfoot diagram showing the many to many relationship between Clerk users and Clerk organizations by using Memberships as the joining entity.

Clerk offers various types of metadata to add arbitrary data to entities in Clerk, and memberships can also contain metadata independent of the organization or user. Toggling on the last column will flag the user as “licensed” in their membership metadata using the following server action:

src/app/licensing/actions.ts

When the licensing page loads, the Clerk Backend API is used to query the memberships of the organization, which includes the metadata used to flag a specific user as licensed in that organization. This both sets the toggle for the user row as well as to aggregate the total number of currently licensed users, which will also be used to determine how many licenses are available.

src/app/licensing/page.tsx

If the currentlyLicensedUsers value is equal or greater than currentLicenseCount and the user is not already licensed, the ability to enable licenses for a user can be disabled:

src/app/licensing/page.tsx

Managing the subscription

Besides rendering a list of users, the /licensing page now also renders the ManageLicensesCard component to display the available of licenses along with a button to manage the current subscription using the Stripe Customer Portal.

The application licensing page showing a "Manage Subscription" card with the number of available seats, number purchased, and a button to manage the subscription. A list of users with toggles to indicate license status is also shown.

The Customer Portal is a hosted solution from Stripe that allows users to manage their own subscriptions without developers having to build a custom user interface.

This feature is off by default, but can easily be enabled in the Stripe Dashboard. Since licenses are managed via Stripe subscriptions, we only need to allow subscription management for our customers for this specific SKU.

The settings page for the Stripe Customer Portal.

As with the Checkout Session for the initial license purchase, a custom URL will be generated based on the Stripe customer ID:

src/app/licensing/actions.ts

After the URL is returned to the front end, the user will be redirected to the Customer Portal where they can adjust their licenses as needed:

The Stripe Customer Portal.

The customer.subscription.updated event from Stripe will also be handled in the route handler to update the license count for a specific organization:

src/app/api/stripe-hooks/route.ts

Process overview

  1. The application generates a Customer Portal URL from Stripe for the active customer.
  2. The user can manage the number of licenses active in their subscription.
  3. Upon updating the subscription, Stripe sends a webhook to the application informing it of the change.
  4. The application updates the license_count in the database.

A process diagram showing the following steps: the application requesting a Customer Portal URL from Stripe, redirecting the user to Stripe to update the licenses, Stripe sending a webhook to the application informing it of the updated licensing count, the application updating the license count in the Neon table.

Accessing license status

Clerk makes it easy to access information about the currently logged-in user, and accessing membership metadata is no exception.

Recall that we stored the isLicensed flag within the membership metadata. To access these values, the sessionClaims object of the auth() function can be used:

src/app/page.tsx

Then you can decide what functionality of the application can be restricted based on that flag, for example:

src/app/page.tsx

Summary

Per-user licensing is a great way to monetize an application. Stripe offers a great suite of tools that allows developers to process transactions and generate revenue from their work. By combining with power of Clerk Organizations with Stripe, you can build a seamless workflow for your users to independently create their own tenants, purchase and assign licenses for those tenants, and change the subscription at any time.