Whop Websites
A Whop Website, your site at <name>.whop.site on Whop’s own hosting, runs RoutingCat in its own code, whether you build it from your terminal with Whop’s CLI or Whop’s AI website builder makes it. This guide sets it up both ways, then covers what changes on a Whop Website: buy buttons, payments, the check of each build, and when your visitors see your defaults.
What RoutingCat does on your Website
Every Whop Website is a TanStack Start app, which Whop’s hosting runs as a Cloudflare Worker:
- It decides each page in your Website’s server. Its server entry,
src/server.ts, wraps the handler TanStack Start renders your pages with in RoutingCat’swhop(), which picks each visitor’s variants before the page renders. The root route hands that decision to every page, which renders its slots withuseSlots; a click to another page asks RoutingCat for that page’s variants. - Your builds register with the key for one Whop Website. Whop’s builds see none of your Website’s secrets, so the key sits in your code, in
routingcat.config.ts, which RoutingCat’s build alone reads. The key names your Website’s address, and RoutingCat checks each build there before it counts, so a copy of the key makes no build of yours live. - Buy buttons go through the Website’s checkout route, which takes the visitor id to Whop’s checkout, so that each sale is credited to the visit that led to it.
- Its Whop payments connect themselves, with no API key of yours and no command.
Set it up from your terminal
In your Website’s folder, which whop apps init made or whop apps pull downloaded, its packages installed, you or your agent run npx routingcat init.
It finds your Website’s address and account. With Whop’s CLI signed in, init reads them from it. Else it asks you at a terminal, or you give them, npx routingcat init --origin https://lumen.whop.site --whop-account biz_8Jk4mWzR2pLs; run by an agent without them, it stops, with exit code 2.
It shows its plan, then applies it. It signs you in, creates your site bound to the Website, makes its edits, then mints the key and writes it into routingcat.config.ts, never into what it prints. On Lumen’s Website, fresh from Whop’s CLI, run by an agent and signed in already:
Checks ✓ Whop Website app_8Fh2Lq9wNz3x (Lumen) · TanStack Start 1.169.4, TanStack Router 1.170.46 · pnpm ✓ https://lumen.whop.site · account biz_8Jk4mWzR2pLs · from Whop's CLI ✓ src/server.ts: none yet → init writes whop() there ✓ wrangler.jsonc: main @tanstack/react-start/server-entry → init names ./src/server.ts ✓ signed in to RoutingCatDetected for outcomes: Whop (payments)
Plan 1 create the site lumen, bound to Whop Website app_8Fh2Lq9wNz3x (Lumen) 2 pnpm add routingcat@1.0.0 3 vite.config.ts add routingcat() from routingcat/vite to plugins, before tanstackStart() 4 src/server.ts new: whop() from routingcat/whop around TanStack Start's handler 5 wrangler.jsonc set main to ./src/server.ts 6 src/routes/__root.tsx add beforeLoad: routingcatBeforeLoad and <RoutingCat /> after the page in <body> 7 routingcat.d.ts new: the types of virtual:routingcat/values 8 routingcat.config.ts new: the site and its public key 9 routingcat.config.ts deployKey: the key for one Whop Website, for https://lumen.whop.site 10 the skill .agents/skills/routingcat/, copied to .claude/skills/routingcat/Each edit has a purpose:
| File | What init does |
So that |
|---|---|---|
vite.config.ts |
adds routingcat(), before tanstackStart() |
each build registers what changed, with the key |
src/server.ts |
writes whop(handler, { build }), in place of a wrapper that only hands each request on, as Whop’s AI builder writes one |
RoutingCat decides each page before TanStack Start renders it, and answers its own paths, under /_rc/ |
wrangler.jsonc |
names ./src/server.ts as main |
Whop runs that server entry |
src/routes/__root.tsx |
adds beforeLoad: routingcatBeforeLoad, and <RoutingCat /> after the page |
every page reads its visitor’s variants, and the browser reports what it showed |
routingcat.d.ts |
declares virtual:routingcat/values, the build src/server.ts imports |
your project typechecks |
routingcat.config.ts |
creates it, with your site, its public key, your Website’s address as origin, and the key, deployKey |
the build registers with the key; it reads this file alone |
A server entry of your own, which does more than hand each request to TanStack Start, stays yours: init prints how to hand its requests to RoutingCat’s whop(), and edits nothing there. A root route with a beforeLoad of its own stays yours too, and init prints the one that returns both contexts: beforeLoad: async (options) => ({ ...(await routingcatBeforeLoad(options)), ...yours }). The skill and the note in AGENTS.md come as on every site: see Coding agents.
Once your agent has finished setup, deploy as you do, whop apps deploy. RoutingCat checks the build at your Website’s address at its first visit, as below.
A new key comes from npx routingcat keys deploy --whop, run by the site’s owner, which shows it once, with the line to put in routingcat.config.ts; --rotate replaces it, the old one working for 24 hours.
Set it up with Whop’s AI builder
Whop’s AI builder has no terminal, so you give it a prompt it follows step by step, with your site’s values in it. The dashboard writes it for you: in Add a site, or Add your first site before you have one, choose A Whop Website with the Whop AI builder, type your Website’s address and your Whop account’s id, biz_…, which the Whop dashboard’s address shows, then Create the site. It creates your site, bound to your Website, and its key for one Whop Website, and shows the prompt with your site’s name, its public key and that key in place, the key there alone, once.
Copy the prompt into the builder, as the dashboard gives it, your values in place of each <…>:
Install RoutingCat on this website. RoutingCat picks each visitor's variants and measures sales. Make exactly the changes below, then reply with the list of files you changed and the first line of src/server.ts.
1. Add the npm package routingcat to the dependencies, at its latest version, and install it.2. In vite.config.ts, import routingcat from 'routingcat/vite', and add routingcat() to the plugins, right before tanstackStart().3. Make src/server.ts exactly this file, in place of any wrapper it has: import build from 'virtual:routingcat/values' import handler from '@tanstack/react-start/server-entry' import { whop } from 'routingcat/whop'
export default whop(handler, { build })4. In wrangler.jsonc, set "main" to "./src/server.ts".5. Create routingcat.d.ts, beside package.json, with exactly this content: declare module 'virtual:routingcat/values' { const build: import('routingcat/whop').RoutingCatBuild export default build }6. In src/routes/__root.tsx, import { RoutingCat, routingcatBeforeLoad } from 'routingcat/tanstack-start'. Add beforeLoad: routingcatBeforeLoad to createRootRoute({ … }), and put <RoutingCat /> inside <body>, right after {children}.7. Create routingcat.config.ts, beside package.json, with exactly this content: import { config } from 'routingcat/tanstack-start'
export default config({ site: '<your site>', publicKey: '<your public key>', origin: 'https://<your Website's name>.whop.site', objective: 'purchase', })
export const deployKey = '<your key for one Whop Website>' This key works only for builds Whop serves at this Website's address, so it is safe in the code. Import routingcat.config.ts from no other file: only RoutingCat's build reads it.8. Change every buy button that links to https://whop.com/checkout/plan_… to link to /_rc/whop/checkout/plan_…, with the same plan id and any ?a= it has, as a plain <a href>, never the router's <Link>.9. Remember these rules, and follow them in every later change to this website: This site runs RoutingCat, which picks each visitor's variants and measures sales. Before changing a page's content, a buy button, src/server.ts, vite.config.ts or src/routes/__root.tsx, read node_modules/routingcat/skills/routingcat/SKILL.md and the Whop guide it names. Always keep whop() in src/server.ts, routingcat() in vite.config.ts, and routingcatBeforeLoad and <RoutingCat /> in the root route. Write variants with useSlots. Link buy buttons to /_rc/whop/checkout/<plan>, never straight to whop.com/checkout. Import routingcat.config.ts from no other file.Check its answer before you publish. The builder lists the files it changed, src/server.ts, vite.config.ts, wrangler.jsonc, src/routes/__root.tsx, routingcat.d.ts, routingcat.config.ts, your pages with buy buttons and package.json, and the first line of src/server.ts, import build from 'virtual:routingcat/values'. A builder can say it is done without having changed anything, so ask again for what is missing. Then publish the Website in the builder: RoutingCat checks its build at your Website’s address at its first visit, and npx routingcat doctor shows the build once it reports in.
Keep the rules in its memory. The prompt’s last step asks the builder to remember RoutingCat’s rules, so that its later edits keep RoutingCat working. Where it forgets them, ask it to keep these:
This site runs RoutingCat, which picks each visitor’s variants and measures sales. Before changing a page’s content, a buy button,
src/server.ts,vite.config.tsorsrc/routes/__root.tsx, readnode_modules/routingcat/skills/routingcat/SKILL.mdand the Whop guide it names. Always keepwhop()insrc/server.ts,routingcat()invite.config.ts, androutingcatBeforeLoadand<RoutingCat />in the root route. Write variants withuseSlots. Link buy buttons to/_rc/whop/checkout/<plan>, never straight towhop.com/checkout. Importroutingcat.config.tsfrom no other file.
Write variants
A page declares its slots in routingcat/, one file each, with its routes, and renders them with useSlots, each variant’s fields in its slot:
import { page, slot, variant } from 'routingcat/tanstack-start'
export const home = page('home', { routes: ['/'], slots: { hero: slot({ default: 'current', variants: { current: variant({ fields: { headline: 'Edit photos like a pro' } }), oneclick: variant({ fields: { headline: 'Pro-level photo edits in one click' } }), }, }), },})function Home() { const x = useSlots(home) return ( <main> <x.hero>{(f) => <h1>{f.headline}</h1>}</x.hero> <Link to="/pricing">Pricing</Link> </main> )}The default is what the page shows today, and the one the control always sees. Your Website’s root route carries RoutingCat’s two parts, as init writes them:
import { RoutingCat, routingcatBeforeLoad } from 'routingcat/tanstack-start'
export const Route = createRootRoute({ beforeLoad: routingcatBeforeLoad,… <body> {children} <RoutingCat /> <Scripts /> </body>Link buy buttons through the checkout route
Link each buy button to your Website’s checkout route, /_rc/whop/checkout/<plan>, in place of Whop’s plain link: https://whop.com/checkout/plan_wsdJzb3gEKcPK becomes /_rc/whop/checkout/plan_wsdJzb3gEKcPK. The route creates a checkout for the plan that carries the visitor id, then sends the buyer on to Whop’s checkout; back from it, the buyer lands on the page of your site the route’s return names, as /_rc/whop/checkout/plan_wsdJzb3gEKcPK?return=/thanks, else on the page they came from, and the visit keeps its source.
- A plain
<a href>, never the router’s<Link>, whose click would render a page in the browser instead of reaching the route. - It never stands in the way of a sale. A bot, a prefetch, a visit with no visitor id, or a failure of Whop’s within 3 seconds goes to the plain link instead, and so does a link with a parameter other than the affiliate’s code,
?a=, which the checkout keeps. - Plain links carry no visitor id, so a sale made through one is credited to no visit. Whop’s AI builder links every buy button to one, and
npx routingcat doctornames each, with the route that replaces it.
Payments connect themselves
At its first visit once RoutingCat has checked a build, your Website makes its webhook to RoutingCat in your Whop account, once, with the credential Whop’s hosting gives it, and hands RoutingCat its secret: no API key of yours, and no npx routingcat connect whop, which is for sites outside Whop and refuses here: a second webhook would send your account’s sales twice. The connection then waits for a first real sale to show the account is yours, since Whop’s test event names no account: until then npx routingcat doctor says it waits. Whop lists the outcomes it tracks; declare purchase at least, as the prompt does.
A connection that breaks repairs itself the same way. If Whop disables the webhook, or its deliveries carry a signature RoutingCat cannot verify, your Website deletes its webhook and makes a new one within minutes of its next visit, with no command to run. RoutingCat keeps the account the connection proved, and payments count again from then on. npx routingcat doctor shows whether the repair is done.
Removing the connection, in the site’s settings or with npx routingcat disconnect whop, turns your Website’s payments off: within minutes of its next visit, your Website deletes its webhook and makes none, and RoutingCat records no payment from then on, keeping those it recorded before. Connect payments again, on the connection’s line in the site’s settings, has your Website make a new connection within minutes of its next visit. To move your Whop account’s payments to another of your Websites, remove the connection on the first: the second connects them itself within minutes of its next visit. To move them back, remove the connection on the second, then choose Connect payments again on the first.
One Whop account’s payments feed one site, so a Whop business with several Websites credits its sales on the one that connected them; the others route their visitors and credit no sale.
The check at your Website’s address
Each deploy’s build registers with the key, and holds its changes until RoutingCat checks it at your Website’s address, which its build log says:
routingcat HELD until https://lumen.whop.site answers with this build: RoutingCat checks it there at its first visit, then makes its changes liveAt the build’s first visit, RoutingCat asks your Website’s address which build answers there, at /_rc/v1/deployment, which RoutingCat’s own code in your Website answers. Once it answers with the new build, that build counts, and its changes go live within about 2 minutes. npx routingcat doctor shows the check:
lumen · production ✓ address check https://lumen.whop.site answers dp_01m3yfn1m0exhr06xh9d10mvbr (Whop build bld_0001) · checked 14:21:10A build whose address answers with nothing of RoutingCat’s never counts: the Website live there was deployed without whop() in src/server.ts.
Upgrade
- From a terminal, in your Website’s folder, run
npx routingcat@latest upgrade, then deploy,whop apps deploy. It installs the latest version with your package manager, brings the skill level with it, and shows what changed since your version, from the changelog, and what to do, if anything. - With Whop’s AI builder, which has no terminal, ask it to set
routingcatinpackage.jsonto the latest version, which the changelog names, to install the packages again, then publish. The rules it keeps send it tonode_modules/routingcat/, which then holds that version’s skill and docs.
What serves your defaults, and when
Your visitors see your defaults, and nothing about them is recorded, until RoutingCat has checked a build at your address. After that:
- the control, 10% of visitors, always sees them, so that what routing adds is measured;
- a page TanStack Start prerenders is a file Whop serves before RoutingCat runs, which the build names;
- a page RoutingCat cannot decide in time renders its defaults, and so does a page no file in
routingcat/declares; - server functions and API routes, under
/api/, reach TanStack Start as they are.
A build without its key stops before it registers anything, and so does one whose code would carry the key to your pages: import routingcat.config.ts from none of your Website’s code, which npx routingcat doctor checks.
Next
RoutingCat routes each visitor of your site to the variants that convert.