Refactor high-complexity React components in Dify frontend. Use when `pnpm analyze-component...
npx skills add raintree-technology/claude-starter --skill "whop"
Install specific skill from multi-skill repository
# Description
Whop platform expert for digital products, memberships, and community monetization. Covers memberships API, payments, courses, forums, webhooks, OAuth apps, and checkout integration. Build SaaS, course platforms, and gated communities. Triggers on Whop, memberships, digital products, course platform, community monetization, Whop API, license keys.
# SKILL.md
name: whop
description: Whop platform expert for digital products, memberships, and community monetization. Covers memberships API, payments, courses, forums, webhooks, OAuth apps, and checkout integration. Build SaaS, course platforms, and gated communities. Triggers on Whop, memberships, digital products, course platform, community monetization, Whop API, license keys.
allowed-tools: Read, Write, Edit, Grep, Glob, Bash
model: sonnet
license: MIT
metadata:
author: raintree
version: "1.0"
Whop Platform Expert
Whop is a platform for selling digital products, memberships, courses, and community access.
When to Use
- Building membership/subscription products
- Integrating Whop checkout and payments
- Managing courses and educational content
- Gating content by membership status
- Handling webhooks for payment events
- Building Whop apps with OAuth
Quick Start
Authentication
const headers = {
Authorization: `Bearer ${process.env.WHOP_API_KEY}`,
"Content-Type": "application/json",
};
const response = await fetch("https://api.whop.com/api/v5/me", { headers });
API Key Types:
- Company API Keys - Your own company data (Dashboard → Settings → API Keys)
- App API Keys - Multi-tenant apps (Dashboard → Developer → Your App)
Core Operations
Create Product
const product = await fetch("https://api.whop.com/api/v5/products", {
method: "POST",
headers,
body: JSON.stringify({
title: "Premium Membership",
description: "Access to all content",
visibility: "visible",
}),
});
Create Plan
const plan = await fetch("https://api.whop.com/api/v5/plans", {
method: "POST",
headers,
body: JSON.stringify({
product_id: "prod_xxx",
billing_period: 1,
billing_period_unit: "month",
price: 2999, // $29.99 in cents
currency: "usd",
}),
});
Create Checkout
const checkout = await fetch(
"https://api.whop.com/api/v5/checkout-configurations",
{
method: "POST",
headers,
body: JSON.stringify({
plan_id: "plan_xxx",
success_url: "https://yourapp.com/success",
cancel_url: "https://yourapp.com/cancel",
metadata: { user_id: "123" },
}),
}
);
const { url } = await checkout.json();
// Redirect user to url
Membership Management
List Memberships
const response = await fetch(
"https://api.whop.com/api/v5/memberships?valid=true",
{ headers }
);
const { data: memberships } = await response.json();
Check User Access
async function checkAccess(userId: string, productId: string): Promise<boolean> {
const response = await fetch(
`https://api.whop.com/api/v5/memberships?user_id=${userId}&product_id=${productId}&valid=true`,
{ headers }
);
const { data } = await response.json();
return data.length > 0;
}
Validate License Key
async function validateLicense(licenseKey: string): Promise<boolean> {
const response = await fetch(
`https://api.whop.com/api/v5/memberships?license_key=${licenseKey}`,
{ headers }
);
const { data } = await response.json();
return data.length > 0 && data[0].valid;
}
Cancel Membership
await fetch(
`https://api.whop.com/api/v5/memberships/${membershipId}/cancel`,
{
method: "POST",
headers,
}
);
Payments
Get Payment
const payment = await fetch(
`https://api.whop.com/api/v5/payments/${paymentId}`,
{ headers }
);
Refund Payment
await fetch(`https://api.whop.com/api/v5/payments/${paymentId}/refund`, {
method: "POST",
headers,
body: JSON.stringify({
amount: 1000, // Optional: partial refund in cents
}),
});
Courses
Create Course
const course = await fetch("https://api.whop.com/api/v5/courses", {
method: "POST",
headers,
body: JSON.stringify({
product_id: "prod_xxx",
title: "Complete Web Development",
description: "Learn fullstack from scratch",
visibility: "visible",
}),
});
Create Chapter
const chapter = await fetch("https://api.whop.com/api/v5/course-chapters", {
method: "POST",
headers,
body: JSON.stringify({
course_id: "course_xxx",
title: "Introduction to JavaScript",
order: 1,
}),
});
Create Lesson
const lesson = await fetch("https://api.whop.com/api/v5/course-lessons", {
method: "POST",
headers,
body: JSON.stringify({
chapter_id: "chapter_xxx",
title: "Variables and Data Types",
content: "Lesson content in markdown...",
type: "video", // or 'text', 'quiz', 'assignment'
video_url: "https://youtube.com/watch?v=...",
order: 1,
}),
});
Mark Lesson Complete
await fetch(
`https://api.whop.com/api/v5/course-lessons/${lessonId}/mark-as-completed`,
{
method: "POST",
headers: {
Authorization: `Bearer ${userAccessToken}`,
},
}
);
Webhooks
Setup Endpoint
import crypto from "crypto";
export async function POST(req: Request) {
const body = await req.text();
const signature = req.headers.get("x-whop-signature");
// Verify signature
const hash = crypto
.createHmac("sha256", process.env.WHOP_WEBHOOK_SECRET!)
.update(body)
.digest("hex");
if (hash !== signature) {
return new Response("Invalid signature", { status: 401 });
}
const event = JSON.parse(body);
switch (event.action) {
case "payment.succeeded":
await handlePaymentSuccess(event.data);
break;
case "membership.activated":
await grantAccess(event.data);
break;
case "membership.deactivated":
await revokeAccess(event.data);
break;
case "dispute.created":
await handleDispute(event.data);
break;
}
return Response.json({ received: true });
}
Key webhook events:
| Event | Trigger |
|-------|---------|
| payment.succeeded | Payment completed |
| payment.failed | Payment failed |
| membership.activated | Membership started |
| membership.deactivated | Membership ended |
| invoice.paid | Recurring payment succeeded |
| dispute.created | Chargeback initiated |
OAuth Apps
Redirect to Whop OAuth
const authUrl = new URL("https://whop.com/oauth");
authUrl.searchParams.set("client_id", process.env.WHOP_CLIENT_ID!);
authUrl.searchParams.set("redirect_uri", "https://yourapp.com/callback");
authUrl.searchParams.set("scope", "memberships:read payments:read");
// Redirect user
window.location.href = authUrl.toString();
Handle Callback
export async function GET(req: Request) {
const url = new URL(req.url);
const code = url.searchParams.get("code");
const response = await fetch("https://api.whop.com/api/v5/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_id: process.env.WHOP_CLIENT_ID!,
client_secret: process.env.WHOP_CLIENT_SECRET!,
code,
grant_type: "authorization_code",
redirect_uri: "https://yourapp.com/callback",
}),
});
const { access_token, refresh_token } = await response.json();
// Store tokens securely
}
Middleware (Access Control)
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export async function middleware(request: NextRequest) {
const token = request.cookies.get("whop_user_token")?.value;
if (!token) {
return NextResponse.redirect(new URL("/login", request.url));
}
const response = await fetch("https://api.whop.com/api/v5/me/memberships", {
headers: { Authorization: `Bearer ${token}` },
});
const { data } = await response.json();
const hasAccess = data.some((m: any) => m.status === "active" && m.valid);
if (!hasAccess) {
return NextResponse.redirect(new URL("/subscribe", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*", "/premium/:path*"],
};
TypeScript Types
interface WhopMembership {
id: string;
user_id: string;
product_id: string;
plan_id: string;
status: "active" | "paused" | "canceled" | "expired";
valid: boolean;
license_key: string;
renews_at: string | null;
expires_at: string | null;
cancel_at_period_end: boolean;
}
interface WhopPayment {
id: string;
amount: number;
currency: string;
status: "succeeded" | "pending" | "failed";
user_id: string;
product_id: string;
}
Error Handling
async function safeWhopRequest(url: string, options: RequestInit) {
const response = await fetch(url, options);
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || "Whop API error");
}
return response.json();
}
| Error | Meaning |
|---|---|
401 Unauthorized |
Invalid API key |
403 Forbidden |
Insufficient permissions |
404 Not Found |
Resource doesn't exist |
429 Too Many Requests |
Rate limited |
Security Best Practices
DO:
- Verify webhook signatures
- Store tokens encrypted
- Check membership status server-side
- Use HTTPS everywhere
DON'T:
- Expose API keys client-side
- Trust client-sent membership data
- Skip webhook verification
- Log sensitive data
Implementation Checklist
- [ ] Create Whop account and company
- [ ] Get API keys from Dashboard
- [ ] Set environment variables
- [ ] Create products and plans
- [ ] Implement checkout flow
- [ ] Add membership access checking
- [ ] Set up webhook endpoint
- [ ] Handle payment events
- [ ] Test end-to-end flow
Resources
- Dashboard: https://whop.com/dashboard
- API Docs: https://docs.whop.com
- API Base: https://api.whop.com/api/v5
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.