# Catch seat billing gaps between Clerk and Stripe each week

> Every Monday, see which customers use more seats than they pay for and which pay for seats nobody uses, ranked by what each gap is worth.

- Workflow type: agent
- Services: Clerk, Stripe, Slack Bot
- Categories: Finance, Operations
- Published: 2026-08-14

## What it does

- Every Monday morning, compares how many people are actually inside each customer account against how many seats that customer is paying for.
- Splits the gaps in two: accounts using more seats than they bought, which is money you are not collecting, and accounts sitting on empty seats, which is a refund request and a churn signal waiting to happen.
- Ranks every mismatch by what it is worth per month and writes a one-line recommended action for each account, plus a callout for any customer account with no matching subscription at all.
- Posts the whole brief as a single Slack message. Nothing in your billing is touched, so every seat change stays a human decision.

## What you'll need

- A Clerk account where your customers are set up as organizations, connected to your live environment rather than your test one so you are reading real customers.
- A Stripe account with per-seat subscriptions, meaning plans where the quantity on the subscription represents a number of users.
- A Slack workspace and a channel to post the weekly brief in.

## Prompt

Every Monday at 8am, reconcile the seats my B2B customers actually use in Clerk against what they are being billed for in Stripe, then post a prioritized revenue leakage brief to Slack. This workflow reports and recommends only. It must never change billing.

Start in Clerk. Use "Get a list of organizations for an instance" to pull every organization. Pagination is offset based with a default limit of 10 and a maximum of 500, and the response comes back as a {data, total_count} envelope, so page all the way through using total_count or large instances will be silently truncated. That list can return a member count directly, so prefer it over making one membership call per organization. When I need membership detail the list does not carry, such as member email domains, use "Get a list of all organization memberships within an instance" and page it the same way rather than looping per organization. Use my production Clerk secret key (the sk_live_ prefix) so the report reconciles production membership against production billing instead of my development instance.

Then pull billing from Stripe. Use "List Subscriptions" filtered to status=active, paging through the full result set with the cursor. For each active subscription, use "Retrieve Customer" to resolve the account name and email address, since the subscription record alone will not tell me who the customer is. Capture the seat quantity on each subscription line item along with its unit price, currency, and billing interval, so dollar impact can be calculated later. Remember that Stripe amounts are in the smallest currency unit, so cents need converting before anything is shown to a human.

Match each Clerk organization to a Stripe subscription. This is fuzzy and needs judgement, so try these signals in order of reliability: a Clerk organization id stored in the Stripe customer or subscription metadata, which is the strongest match; an exact or near-exact company name match between the Clerk organization name and the Stripe customer name; and the email domain of the Stripe customer checked against the email domains of that organization's members. Say so explicitly when a match is low confidence rather than guessing silently, and keep a separate list of Clerk organizations that match nothing at all.

For every matched pair, compare the active Clerk member count to the seat quantity on the subscription and flag every account where the two diverge. Split the divergences into two groups. Under-billed accounts have more Clerk members than paid seats, which is lost revenue I should be collecting. Over-billed accounts have paid seats sitting empty, which is a likely refund request and a churn risk worth getting ahead of. Estimate the monthly dollar swing for each account from the per-seat unit price multiplied by the delta, normalizing annual plans down to a monthly figure so everything ranks on the same scale. Skip organizations below a configurable minimum member count, defaulting to 3, so tiny accounts do not create noise. Skip any subscription that is not priced per seat, such as flat-rate or usage-based plans, since seat quantity is meaningless there.

Post one message to Slack with "Send a Message" to a configurable channel. List the divergent accounts ranked by monthly dollar impact, highest first, with under-billed accounts ahead of over-billed ones. For each account show the account name, members versus paid seats, the delta, the estimated monthly dollar swing, and a one-line recommended action, such as adding seats at the next renewal or booking a check-in before an over-provisioned account renews. Add a short closing section calling out any Clerk organization with no matching Stripe subscription at all, since those may be using the product without paying. Finish with a one-line net total of uncollected revenue versus revenue exposed to refund requests. Keep the whole thing to a single message a founder or revenue lead can read in under a minute, and when nothing has diverged, say so in one line instead of posting an empty report.

Do not call any Stripe write or update operation, and do not modify anything in Clerk. A human approves every seat change.

## How to customize

- Change the cadence. Monday at 8am keeps the channel quiet, while daily catches revenue faster if seats move around a lot.
- Pick where it lands, whether that is a revenue channel, a founders channel, or a private finance channel.
- Set the minimum account size worth flagging so a one-seat difference on a tiny account does not create noise.
- Choose whether to include over-billed accounts or only report the gaps that are costing you money.

## FAQ

### Will this change what my customers are charged?

No. It only reads your customer accounts and your billing, then reports what it finds. It never adjusts a subscription, adds a seat, or issues a refund. Every change stays with you, which is deliberate, because automatically re-pricing a customer without a human looking first is how you end up with an angry invoice.

### How does it know which subscription belongs to which customer?

It matches on three signals: an account id you already store alongside the subscription, the company name, and the email domain on the billing contact. Matches it is unsure about are called out rather than assumed, and any customer account it cannot match to a subscription at all gets its own section in the brief.

### Does this work if I bill a flat rate instead of per seat?

Plans that are not priced per seat are skipped automatically, because a seat count means nothing on a flat-rate or usage-based plan. If you run a mix, only the per-seat plans get reconciled and the rest are left alone.

### What if a customer is using the product with no subscription at all?

That is one of the most valuable things this catches. Any customer account with active members but no matching active subscription is listed separately, so you can find out whether it is a lapsed plan, a forgotten trial, or a billing record that never got created.

### Can I run it more often than once a week?

Yes. Weekly is a good default because it keeps the Slack channel calm, but you can move it to daily if seats change frequently and you want to capture revenue sooner. The brief stays quiet when nothing has drifted.

Use this prompt in General Input: https://www.generalinput.com/prompts/catch-seat-billing-gaps-between-clerk-and-stripe-each-week