Connect Clerk
Use Clerk user and organization metadata as your access source instead of Postgres.
Use Clerk as your access source instead of connecting a database when your app stores plan, premium, and trial in Clerk user or organization metadata rather than a Postgres table.
You still connect Stripe for billing — Clerk only replaces the database step. Checks, access incidents, and the Access Ledger all work exactly the same.
Before you start
- Your app stores plan, premium, or trial in Clerk user (or organization) metadata — not a Postgres table. If it’s in Postgres instead, connect a database rather than Clerk.
- A Clerk Backend API secret key from Clerk Dashboard →
API keys → Secret keys—sk_test_for your development instance,sk_live_for production. - Stripe still connected on step 2 of onboarding. Clerk only replaces the database step, not billing.
- Admin role on the ParityRail project — connecting Clerk requires admin.
- The Stripe customer id stored somewhere in Clerk user metadata — that’s what ParityRail matches on.
Connect Clerk to ParityRail
- In the Clerk Dashboard, open
API keys → Secret keysand copy your secret key (starts withsk_). - In ParityRail onboarding, go to step 3 (Database) and click the Clerktab in the source toggle — the tablist labeled “Access-state source,” with options “Postgres / Supabase” and “Clerk.”
- Paste the key into the Clerk Backend API secret key field (placeholder
sk_test_…). - Click Test & connect. The button reads “Testing & connecting…” while ParityRail verifies the key by calling Clerk’s
GET /users?limit=1before saving it. - Step 4 (Identity mapping)then shows “Customer matching is automatic” — just click Continue. Clerk matches each user to a Stripe customer by the metadata field you set next.
- Step 6 (Access mapping)asks “Which Clerk fields say who has paid access?” ParityRail samples your recent users and offers the metadata keys it finds as options. Map at minimum the Stripe customer id field and either a plan or premium field, then click Save and continue.
Field paths, ready to paste
The secret key you paste in step 2 above looks like this — copy the shape, not the placeholder:
sk_test_<...> secret key for your development instance
sk_live_<...> secret key for productionEach mapped field on step 6 is a path shaped <container>.<key> — for example, the required Stripe customer id field:
public_metadata.stripe_customer_idOther fields you can map follow the same shape:
public_metadata.plan
public_metadata.is_premium
public_metadata.trial_ends_at
public_metadata.entitlementsThe container is one of:
public_metadataprivate_metadataunsafe_metadata
| Field | Example path | Required | Why |
|---|---|---|---|
| Stripe customer id | public_metadata.stripe_customer_id | Yes | Matches a Clerk user or organization to its Stripe customer — required to reconcile. |
| Plan | public_metadata.plan | Optional | The plan value your app stores, compared against the Stripe subscription's plan. |
| Premium flag | public_metadata.is_premium | Optional | A truthy flag marking paid access (true/1/yes/on/premium/active all read as true). |
| Trial end | public_metadata.trial_ends_at | Optional | When the trial ends, as an ISO string or epoch, compared against the Stripe trial. |
| Entitlements | public_metadata.entitlements | Optional | An object of { featureKey: boolean }; its keys become the subject's entitlements. |
On step 6, ParityRail samples your users (and organizations, if enabled) and suggests the metadata keys it finds — you usually pick from a dropdown instead of typing paths by hand. Organization billing works too: enable the organizations section and map the org metadata field that holds the Stripe customer id and, optionally, the plan.
Verify it worked
- Connecting succeeds with a toast reading
Clerk connected — <masked label>, plus a green Clerk connected card on step 3 showing the masked key label (for examplesk_test_…abcd). - Step 4 renders the Customer matching is automaticpanel — a wand icon plus “Matched automatically for Clerk” — instead of the SQL table/column pickers. The left-rail hint for step 4 also changes to “Matched automatically.”
- On step 6, the field dropdowns are populated with your sampled metadata keys, shown as
public_metadata.<key>. Seeing real keys there confirms ParityRail could read your Clerk metadata.
Troubleshooting
| You see | What it means | Fix |
|---|---|---|
Clerk rejected the secret key (HTTP 401)… | Wrong or revoked key, or you pasted a publishable key (pk_) instead of a secret key. | Recopy the Secret key from Clerk Dashboard → API keys → Secret keys. |
Enter a Clerk Backend API secret key (sk_test_… or sk_live_…) | The value you pasted doesn't match the sk_ pattern. | Paste the full secret key exactly as Clerk shows it — no extra spaces or truncation. |
No metadata keys were found on your Clerk users. | None of the sampled users have any metadata set yet (shown on step 6). | Store the Stripe customer id (and plan/premium) in a user’s publicMetadata or privateMetadata in Clerk, then click Retry — ParityRail re-samples your recent users. |
Clerk rate limit hit (HTTP 429)… | Too many requests hit Clerk's Backend API in a short window. | Wait a moment, then retry — ParityRail already backs off automatically before surfacing this. |
| Expecting ParityRail to fix Clerk metadata automatically | Clerk repairs are guided, not automated — ParityRail reads Clerk read-only and never writes metadata back. | Apply the exact user/field/value change the access incident shows, in Clerk, then re-run the check to verify it took effect. |
Repairs are guided, not automated
For a Postgres source, an approved repair writes a column directly using a narrow repair role. Clerk is different: ParityRail reads it read-only and does not write metadata back.
When it finds an access incident on a Clerk-sourced project, it files the incident with full evidence and the exact metadata change to make — which user or organization, which field, and the expected value.
You apply that change in Clerk (or your own admin tooling), then re-run the check to verify it took effect.