Skip to content

Whop

Whop takes the payment and keeps the membership, so RoutingCat hears of both from Whop. npx routingcat connect whop connects your Whop company: RoutingCat then receives Whop’s webhooks and turns each into the outcomes it makes, with no webhook code of yours. Your part is the checkout: it carries the visitor id, so that each payment joins the visit that led to it. This guide covers both, and what to keep apart from Whop’s own features. It builds on Track conversions.

Connect your Whop company

The site’s owner runs npx routingcat connect whop once, on their own machine:

  • It asks for a Whop API key, from WHOP_API_KEY or typed at the terminal, never written to disk. Create it in Whop’s dashboard with developer:manage_webhook; the command creates the webhook in your company with it.
  • --reconcile also hands RoutingCat the key, kept encrypted, so that it can read back payments a failing webhook missed. The key then needs payment:basic:read, member:basic:read and plan:basic:read too. Without it, a period Whop failed to deliver can’t be recovered.
  • --prove hands the key over once, to prove the company is yours; RoutingCat keeps nothing.
  • --sandbox connects your Whop sandbox instead of the live company.

It then has Whop send its test event, which RoutingCat verifies and drops, and lists Whop’s own experiments on the company:

$ routingcat connect whop --sandbox --site lumen
✓ whop connected biz_8Jk4mWzR2pLs (sandbox) · signature verified
Whop's own experiments run on this account, apart from RoutingCat's:
Hero copy (hero_copy) since 2026-10-12, on product prod_8Jk4mWzR2pLs
Checkout layout (checkout_layout)
where one changes a page RoutingCat routes, both tests blur: give each page one owner, or pass RoutingCat's membership to Whop as a targeting property

Until Whop’s first delivery proves the company is the one named, the connection waits, as doctor says. npx routingcat connections lists it with its latest delivery.

Pass the visitor id at checkout

Whop copies a checkout configuration’s metadata to the payment and the membership it makes, so your server creates the checkout with the visitor id in it.

The example app’s pricing page does it in a Server Function:

app/pricing/whop.ts
'use server'
import { cookies, headers } from 'next/headers'
import { redirect } from 'next/navigation'
import { WHOP_API_VERSION } from 'routingcat/outcomes'
/**
* The Whop checkout's Server Function: a checkout configuration for the plan WHOP_PLAN_ID names,
* carrying RoutingCat's visitor id in its metadata, which Whop copies to the payment and the
* membership it makes. The site's Whop connection reports them to RoutingCat.
*/
export async function startWhopCheckout(): Promise<never> {
const h = await headers()
const origin = `${h.get('x-forwarded-proto') ?? 'https'}://${h.get('host')}`
const visitorId = (await cookies()).get('rc_vid')?.value
// WHOP_SANDBOX=1, as the example is deployed, uses Whop's sandbox, so it never takes real money.
const api =
process.env.WHOP_SANDBOX === '1'
? 'https://sandbox-api.whop.com/api/v1'
: 'https://api.whop.com/api/v1'
const response = await fetch(`${api}/checkout_configurations`, {
method: 'POST',
headers: {
authorization: `Bearer ${process.env.WHOP_API_KEY ?? ''}`,
'api-version-date': WHOP_API_VERSION,
'content-type': 'application/json',
},
body: JSON.stringify({
plan_id: process.env.WHOP_PLAN_ID,
metadata: visitorId ? { rc_vid: visitorId } : undefined,
// rc_return=whop: coming back from the checkout keeps the visit's source.
redirect_url: `${origin}/pricing?checkout=done&rc_return=whop`,
}),
})
if (!response.ok) throw new Error(`Whop refused the checkout configuration: ${response.status}`)
const { purchase_url } = (await response.json()) as { purchase_url?: string }
redirect(purchase_url ?? '/pricing')
}

Its button, beside Stripe’s in the same form:

app/pricing/page.tsx
<form action={startCheckout}>
<button type="submit">Pay the first month</button>
<button type="submit" formAction={startWhopCheckout}>
Pay with Whop
</button>
</form>
  • metadata: { rc_vid }, from the rc_vid cookie, only when the visitor has one.
  • rc_return=whop on redirect_url. The buyer’s return from Whop’s checkout is then a return, not a new arrival, so sources still credits the purchase to where the visit came from.
  • api-version-date, routingcat/outcomes’ WHOP_API_VERSION, today 2026-10-09-2: the newest version of Whop’s API that RoutingCat’s mapping reads.
  • Test in Whop’s sandbox first. With WHOP_SANDBOX=1, the function creates the checkout in Whop’s sandbox, with the sandbox’s own WHOP_API_KEY and WHOP_PLAN_ID, and the site connected with npx routingcat connect whop --sandbox: nothing is charged. Anything else uses Whop’s live API, so go live with the live key and plan, and the live connection.
  • A plain plan link carries no metadata, so a purchase made through one arrives with no visitor to join it to: create every checkout on your server, as above.

What the connection tracks

Outcome When
purchase A one-time payment, or a membership’s first payment above zero, after a trial included, marked after_trial
renewal Each later payment of a membership
trial_started A membership that starts with a trial
cancel_scheduled, cancel_effective A membership set to end, and one that has ended
refund A refund that succeeded, as a negative value, or reversed
dispute A dispute’s opening and its end

Values are in major units, without tax; a payment in a currency that isn’t one of ISO 4217’s counts in US dollars. Declare in outcomes the ones you want reported, purchase at least. Refunds and disputes need no declaring: results link each to the purchase it corrects.

Keep Whop’s features apart

  • Whop’s own store pages and its hosted checkout take no code of yours, so RoutingCat can’t route them. Route your own site’s pages, and send the buyer to Whop’s checkout from there.
  • No affiliate_code or ?a= in a variant: it pays a commission and changes Whop’s attribution.
  • Whop’s own experiments, which connect whop lists: where one changes a page RoutingCat routes, both blur. Give each page one owner, or keep a visitor out of both.

Next