One Login Across Seven Websites for $0: How to Add Google Sign-In and Stripe Payments with Clerk and Cloudflare D1 (Clerk + Cloudflare D1 + Stripe)
How to Add Google Sign-In and Stripe Payments to Your Web App with Clerk and Cloudflare: The Exact Steps
A practical guide to authentication, Cloudflare D1, Stripe and shared logins across subdomains, with lessons from building accounts for my free party game: One Login Across Seven Websites, for $0:
Stripe + Clerk + Cloudflare D1 + Google sign-in, click by click — including the five traps that waste an afternoon
What you’ll learn
- How to connect Clerk, Google sign-in, and Cloudflare D1.
- How to keep account records and purchases in sync
- How authentication, customer records, and paid access fit together.
- Why your domain structure matters for shared logins.
- How to test the experience beyond a successful login screen.
- Where free plans help — and where their limits begin.
I run seven country editions of a browser game, each on its own website. I needed one account that works on all of them, a record of who signed up and who paid, and a monthly bill of zero.
This is the build, in the order you should do it. Every value you need to type or paste is here. Substitute yourgame.com for your own domain throughout.
What you’ll have at the end: sign-in with email or Google on every site, one account shared across all of them, a database recording signups and purchases, and refunds that automatically take back whatever the purchase unlocked.
Cost: free to 50,000 monthly users (Clerk), free database (Cloudflare D1), free Google sign-in.
Before you start: your sites hosted on Cloudflare Pages, your domain’s DNS on Cloudflare, and a Google account.
Step 1 — Put every site on one domain
Do this first. Auth providers share a login across subdomains for free. Across different domains it’s a paid feature — for Clerk, Pro plus $10/month per extra domain. Seven sites on seven domains would have cost me $100+/month for something that’s otherwise free.
For each site:
- Cloudflare dashboard → Workers & Pages → click your project
- Custom domains tab → Set up a custom domain
- Type the subdomain:
ke.yourgame.com→ Continue - Cloudflare shows the CNAME it will create → Activate domain

Repeat for each site: ke., gh., eg., et. and so on. The old *.pages.dev addresses keep working.
Give it two minutes for the certificate, then load each address over https.
Don’t miss this: if you have a hub or landing page linking to your other sites, update those links to the new subdomains now. Anyone arriving at a site via an old pages.dev link is on a different domain and will appear signed out — which defeats the entire exercise. I had seven of these links and nearly shipped them.
Step 2 — Set up Clerk
2a. Create the application
- dashboard.clerk.com → create application
- Project type: Consumer (B2B is for teams with roles)
- Name it — this appears in the sign-in box, so use your real product name
- Sign-in options: turn on Email and Google. Turn off phone (every SMS costs money) and username
- Create application

You’re now in a Development instance, which only works on localhost. Ignore the framework setup wizard it shows you — that’s for Next.js/React apps.
2b. Create the production instance
Click Go to prod (top right) → choose Clone development instance → Continue.

Cloning carries over the sign-in options you just picked. “Create default instance” makes you redo them.
2c. Add your domain
When asked for the domain, enter the apex:
Plaintext
yourgame.comNot www., not a subdomain. The apex is what makes the login work across every subdomain for free.


That “Satellites — Pro” tab is the paid cross-domain feature you’re avoiding by doing Step 1.
2d. Add the DNS records
Configure → Developers → Domains → click Configure next to your domain. You get about five CNAME records:

- Name:
clerk| Points to:frontend-api.clerk.services - Name:
accounts| Points to:accounts.clerk.services - Name:
clkmail| Points to:mail.<id>.clerk.services - Name:
clk._domainkey| Points to:dkim1.<id>.clerk.services - Name:
clk2._domainkey| Points to:dkim2.<id>.clerk.services

The first two are the login itself; the last three let Clerk send verification emails from your domain.
Easiest route: click Configure automatically at the top. Clerk connects to Cloudflare and adds all five itself, with the correct proxy setting. Review the confirmation screen and click Authorize.

Adding them by hand? In Cloudflare → DNS → Add record, set type CNAME, name exactly as shown (Cloudflare appends your domain), and — critically — Proxy status: DNS only, the grey cloud. Proxied records make verification fail forever with no useful error.
Then click Verify Records in Clerk. Frontend API and Account portal verify first, email follows, SSL certificates come last.
2e. Get your keys
Instance → API keys (bottom of the left sidebar — not Developers → API keys, which is a different feature).

Copy both:
- Publishable key (
pk_live_…) — public by design, goes in your website code - Secret key (
sk_live_…) — master access to every account. Dashboard only, never in a file, never in a screenshot
Step 3 — Configure Google sign-in
Production instances can’t use Clerk’s shared test credentials, so you need your own free Google OAuth app.
3a. Get the redirect URI from Clerk
Configure → SSO connections → click Google (“Setup required”). Copy the Authorized Redirect URI:
Plaintext
https://clerk.yourgame.com/v1/oauth_callback
Leave that tab open.
3b. Create a Google Cloud project
Go to console.cloud.google.com/projectcreate → name it → Create. Check the project chip at the top switched to your new project before continuing.
No billing account is needed. OAuth sign-in is free.
3c. Fill in the consent screen
console.cloud.google.com/auth/overview → Get started:
- App name: your product name
- User support email: your address
- Audience: External
- Developer contact: your address
Then Branding, where you fill in:

- Application home page:
[https://yourgame.com] - Privacy policy link:
[https://yourgame.com/privacy.html] - Terms of service link:
[https://yourgame.com/terms.html] - Authorized domains → Add domain:
yourgame.com(bare, nohttps://)

Do not upload a logo. Despite what my screenshot above says.
A logo triggers Google’s brand verification: days of review that can bounce back. Without one, and asking only for name and email, you publish instantly.
3d. Publish it
console.cloud.google.com/auth/audience → Publish app → Confirm.
[IMAGE: Screenshot from 2026-09-24 11-55-40.png — the "Push to production?" dialog]
Leave it in “Testing” and only email addresses you manually list can sign in, capped at 100.
3e. Create the client and paste it back
console.cloud.google.com/auth/clients → Create client:
- Application type: Web application
- Name:
Clerk - Authorized redirect URIs → Add URI → paste the URI from 3a, exactly, no trailing slash
- Create
Copy the Client ID and Client secret into the Clerk tab → Save.
⚠️ Google’s confirmation dialog displays the secret in full. Don’t screenshot it. If you already did, rotate it afterwards: Clients → your client → add a new secret → update Clerk → delete the old one.
Step 4 — Create the database
4a. Create it
Cloudflare → Storage & Databases → D1 SQL Database → Create:
- Name:
yourgame - Data location: expand Provide a location hint and pick the region nearest your players. Left blank, Cloudflare places it near you, which may be the wrong continent. It can’t be changed later without recreating
- Specify jurisdiction: leave alone — it’s for EU data residency and overrides the location hint

4b. Create the tables
Open the database → Console tab → paste your schema → Execute.
Write every statement as CREATE TABLE IF NOT EXISTS so it's safe to run twice. Four tables carried me: users, identities (one row per way of signing in), purchases, entitlements.
Two design rules worth copying:
- Key everything on your own user ID, not the auth provider’s. Store the provider’s ID in
identities. That's what lets one person use email today and Google tomorrow, and what saves you if you ever change provider - Give purchases a status (
paid/refunded/disputed) and let entitlements be revoked with a reason, never deleted. Without this, someone can buy, refund, and keep what they bought forever
Verify in the Tables tab.
4c. Connect it to your site
Workers & Pages → your project → Settings → Bindings → Add → D1 database:
- Variable name:
DB(this is the name your code looks for) - D1 database: your database
- Add it, then repeat for Preview if you use preview deployments

Then redeploy. See the trap in troubleshooting — this one caught me three times.
Step 5 — Connect authentication to your code
5a. The publishable key goes in your site
One line in your front-end config:
JavaScript
publishableKey: 'pk_live_…'It ships in your page source. That’s fine — it identifies your Clerk instance and grants nothing on its own.
5b. The secrets go in the hosting dashboard
Workers & Pages → your project → Settings → Variables and secrets → Add:

- Name:
CLERK_PUBLISHABLE_KEY| Value:pk_live_…| Type: Text - Name:
CLERK_SECRET_KEY| Value:sk_live_…| Type: Secret - Name:
CLERK_WEBHOOK_SECRET| Value: from 5c | Type: Secret - Name:
STRIPE_WEBHOOK_SECRET| Value: from 5d | Type: Secret

Choosing Secret rather than Text means the value becomes unreadable afterwards. That’s what you want.
5c. The Clerk webhook — records signups as they happen
Clerk → Configure → Webhooks → Add Endpoint:
- URL:
https://yourproject.pages.dev/api/clerk/webhook - Events:
user.created,user.updated,user.deleted - Create → copy the Signing Secret into
CLERK_WEBHOOK_SECRET

5d. The Stripe webhook — the one that protects your money
First, check you’re in the right Stripe account. Your site’s publishable key pk_live_51ABC… encodes the account ID acct_1ABC…; compare it with Settings → Account details. I have two Stripe accounts and events sent from the wrong one would simply never arrive.
Then dashboard.stripe.com/webhooks, in Live mode → Add destination.
Select these five events:
Plaintext
checkout.session.completed
checkout.session.async_payment_succeeded
charge.refunded
charge.dispute.created
charge.dispute.closed
The first two record sales. The last three are what let you take the benefit back when someone refunds or charges back — and restore it if you win a dispute. Skip them and you’re running an honour system.
Destination type Webhook endpoint, then:
- Endpoint URL:
[https://yourproject.pages.dev/api/stripe/webhook](https://yourproject.pages.dev/api/stripe/webhook) - Name it something you’ll recognise in six months

Copy the Signing secret into STRIPE_WEBHOOK_SECRET, then redeploy.
Note: signing secrets are per endpoint. Creating a second endpoint instead of editing the first gives you a new secret and silently breaks the old one.
Step 6 — Test shared sign-in
6a. Add a health check you can trust
Build one endpoint that reports each piece separately and returns booleans only — never a key, never a database error:
JSON
{ "ok": true, "database": true, "databaseQuery": true,
"schema": "002-accounts", "clerk": true,
"clerkWebhook": true, "stripeWebhook": true }database says the binding exists; databaseQuery says it actually works; schema proves the tables are there.

6b. The actual test
- Open one of your sites and sign up with a throwaway email. The code should arrive within seconds — that also proves your email DNS records work
- Try Continue with Google. It should land in the same account, because the email matches
- Now open a different site of yours. You should already be signed in. That’s the whole point

- Check your database:
SELECT * FROM usersshould show the account and which site it came from - Buy something with a Stripe test card, then refund it in Stripe. The benefit should disappear on its own, and the sale should remain on record marked
refunded
Troubleshooting: the five traps
1. You saved the setting and nothing changed.
Cloudflare attaches bindings and variables when a deployment is built. Saving changes nothing until a new build runs. Deployments → latest → ⋯ → Retry deployment, or push any commit. This caught me with the database binding, the Clerk keys, and both webhook secrets.
2. DNS verification stays “pending” forever.
The records are proxied. In Cloudflare they must be DNS only — grey cloud. Proxied records never verify and produce no useful error.
3. “Automatic setup could not be confirmed.”
Often a false alarm — the tool checked faster than DNS propagates.

Before re-running anything, check reality:
Bash
dig +short CNAME clerk.yourgame.com @1.1.1.1If it returns the target, the records are fine — just click Verify Records again. Re-running the automatic setup risks duplicates. (Bonus: if dig returns Cloudflare IP addresses instead of a CNAME target, your record is proxied — that's trap 2.)
4. Your setup check passes when nothing is configured.
If your “am I signed in?” endpoint answers “signed out” before it checks configuration — which is correct behaviour, so a broken deployment never locks players out — then it will look healthy with nothing connected at all. That’s why Step 6a exists. My original instructions had this bug; a reviewer caught it.
5. You can’t find the API keys, or you found the wrong ones.
Clerk has two pages called “API keys”. Developers → API keys is a feature letting your users mint keys — leave it disabled.
Instance → API keys is yours.

Bonus trap — the download that nearly went public. Google’s console saves a client_secret_*.json to your downloads folder. Mine landed in my project folder, which is also a website's root directory. It was never committed, so nothing leaked — but one routine git add -A would have published my OAuth secret at [yourgame.com/client_secret](https://yourgame.com/client_secret)_….json. Add client_secret*.json, *service-account*.json and .env* to your .gitignore before you need them.
What this costs
- Clerk: Free until 50,000 monthly users
- Cloudflare D1: Free until 5 GB, 5M row reads/day
- Cloudflare Pages Functions: Free until 100,000 requests/day
- Google OAuth: Free always

Free-plan trade-offs worth knowing: sessions last 7 days before re-login, there’s a small “secured by Clerk” badge, no two-factor auth, and three social logins.
Total elapsed time: about two days including the mistakes. Following these steps, closer to two hours.
The one decision that determines whether any of this stays free is Step 1. Get your sites onto one domain before you touch an auth provider — everything after that is filling in forms.
I’m a New York-based software and data engineer and a recent master’s graduate. This is just one example of the work I do.
I’m currently open to project-based work, and consulting. If you, your team, or someone in your network could use my skills, I’d love to connect.
I’m always happy to have a coffee chat and explore how I could help. Feel free to reach out.

Comments
Post a Comment