@whop/template-kit
v0.3.0
Published
Data and server layer for building Whop templates on the Whop REST API
Downloads
452
Readme
@whop/template-kit
The data + server layer for building Whop templates — deployable *.whop.app apps whose every screen is backed by the Whop public REST API.
It gives you one typed API contract (WhopAPI), React hooks to read it, a same-origin server proxy that keeps the company API key out of the browser, and a preview bridge so the exact same UI can render with fixtures in a catalog. The reference implementation is the neobank template.
Install
pnpm add @whop/template-kitPeer dependency: react@^19.
Quick start
A template is a TanStack Start app with three pieces of wiring.
1. Declare which endpoints your app may call (src/api-manifest.ts) — select them by name; the kit owns the paths and copy:
import { selectManifest } from "@whop/template-kit";
export const MANIFEST = selectManifest([
"getAccount",
"listPayouts",
"createPayout",
]);This is your allowlist: only the endpoints you name are proxied. (You can still hand-write an EndpointManifest if you need a path the catalog doesn't cover.)
2. Mount the proxy route (src/routes/api.$.ts) — this is the only place the app talks to api.whop.com:
import { createFileRoute } from "@tanstack/react-router";
import { createApiProxy } from "@whop/template-kit/server";
import { MANIFEST } from "../api-manifest";
export const Route = createFileRoute("/api/$")({
server: { handlers: createApiProxy(MANIFEST) },
});3. Provide the API and read it in components:
import { WhopAPIProvider, resolveAPI, useWhopAPI, useEndpoint } from "@whop/template-kit";
function App() {
// resolveAPI() returns the real HTTP client, or the preview bridge when embedded with ?whop_preview=1
return (
<WhopAPIProvider endpoints={resolveAPI()}>
<Balance />
</WhopAPIProvider>
);
}
function Balance() {
const { getAccount } = useWhopAPI();
const { data, isLoading, error } = useEndpoint(getAccount);
if (isLoading) return null;
if (error) return <p>{error.message}</p>;
return <p>{data.total_usd}</p>;
}That's it — no fetch, no URLs, no API key. You call typed methods on WhopAPI; the kit handles transport.
Security model
- The company API key is never in the browser or your app's JS. The client calls same-origin
/api/*with no key; your proxy forwards toapi.whop.comwith noAuthorizationheader; Whop's hosting layer injectsAuthorization: Bearer <company key>at the edge on the outbound hop. - The proxy is unauthenticated by default. Anyone who can reach the app URL gets the account's data back. For anything beyond a public demo, gate
/api/*on the visitor's Whop identity/membership before forwarding. - The manifest is an allowlist. Only declared paths are proxied, and traversal (
..) is rejected before any upstream fetch. - Money-out is scope-ceilinged. The injected key strips money-out scopes, so
POST /transfersandPOST /payoutsreturn403.
Preview mode
The same components render two ways because they only ever talk to a WhopAPI object handed in through WhopAPIProvider:
- Deployed app:
resolveAPI()→httpAPI→ real REST via your/api/*proxy. - Catalog preview: when embedded with
?whop_preview=1,resolveAPI()returnscreatePreviewAPI()— a proxy that answers every call overpostMessagefrom the parent frame. The parent supplies fixtures, so the preview is credential-free.
No fixtures ship in this package or in a deployed template — a deploy is real-data-only by construction.
API
WhopAPI— the typed endpoint contract (account, financial activity, cards, transfers, payouts, swaps, metrics, reports, products, plans, memberships, reviews, payments, checkout configurations).selectManifest/WHOP_ENDPOINTS— build your allowlist by endpoint name from the kit's canonical catalog (paths + copy included).WhopAPIProvider/useWhopAPI/useEndpoint— provide the API and read a single call ({ data, isLoading, error, refresh }).httpAPI/resolveAPI/createPreviewAPI— the real, resolved, and preview implementations ofWhopAPI.httpAPIwraps the official@whop/sdkpointed at your/apiproxy — same client, no key in the browser — and lazy-loads it on first call so it never weighs down your initial bundle.useRealtimeChannel/useRealtimeRefresh— subscribe a component to a realtime channel.@whop/template-kit/server—createApiProxy,proxyConfigFromManifest,createRealtimeRoute,createWebhookRoute,verifyWhopWebhook,publishRealtime.format/currency— display helpers for balances and amounts.
Links
- Whop developer docs
- Reference template:
neobank
Local development
whop apps dev mints a short-lived credential, but the Cloudflare dev runtime doesn't inherit the CLI's environment — give the worker its own vars in a gitignored .dev.vars next to your wrangler.jsonc:
WHOP_API_KEY=<an API key for your business>
WHOP_DEV_ACCOUNT_ID=biz_XXXXXXXXXXXXWhen WHOP_API_KEY is present the proxy attaches it to upstream calls (in the hosted runtime the variable never exists — the edge injects the key instead), and WHOP_DEV_ACCOUNT_ID points getAccount at your business when the credential is a user token. Your template then runs locally against real data.
