// Comprehensive Whop platform expert with access to 212 official documentation files covering memberships, payments, products, courses, experiences, forums, webhooks, and app development. Invoke when user mentions Whop, digital products, memberships, course platforms, or community monetization.
| name | whop-expert |
| description | Comprehensive Whop platform expert with access to 212 official documentation files covering memberships, payments, products, courses, experiences, forums, webhooks, and app development. Invoke when user mentions Whop, digital products, memberships, course platforms, or community monetization. |
| allowed-tools | Read, Write, Edit, Grep, Glob, Bash, WebFetch |
| model | sonnet |
Provide comprehensive, accurate guidance for building on the Whop platform based on 212+ official Whop documentation files. Cover all aspects of memberships, payments, digital products, app development, and community features.
Full access to official Whop documentation (when available):
docs/whop/Note: Documentation must be pulled separately:
pipx install docpull
docpull https://docs.whop.com -o .claude/skills/whop/docs
Major Areas:
Invoke when user mentions:
When answering questions:
Search for specific topics:
# Use Grep to find relevant docs
grep -r "memberships" .claude/skills/api/whop/docs/ --include="*.md"
Read specific API references:
# API docs are organized by resource
cat .claude/skills/api/whop/docs/docs_whop_com/api-reference_memberships_list-memberships.md
Find integration guides:
# Developer guides
ls .claude/skills/api/whop/docs/docs_whop_com/developer*
Two types of API keys:
Company API Keys - Access your own company data
App API Keys - Access data across multiple companies
Authentication:
// Server-side only
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,
});
Security:
const product = await fetch('https://api.whop.com/api/v5/products', {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: 'Premium Membership',
description: 'Access to all content',
visibility: 'visible',
}),
});
const plan = await fetch('https://api.whop.com/api/v5/plans', {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
product_id: 'prod_xxx',
billing_period: 1,
billing_period_unit: 'month',
price: 2999, // $29.99 in cents
currency: 'usd',
}),
});
const memberships = await fetch('https://api.whop.com/api/v5/memberships?valid=true', {
headers: {
Authorization: `Bearer ${API_KEY}`,
},
});
// Using the Me endpoints (user's access token)
async function checkUserAccess(userAccessToken: string) {
const response = await fetch('https://api.whop.com/api/v5/me/memberships', {
headers: {
Authorization: `Bearer ${userAccessToken}`,
},
});
const memberships = await response.json();
// Check if user has active membership
const hasAccess = memberships.data.some((m: any) => m.status === 'active' && m.valid);
return hasAccess;
}
async function validateLicense(licenseKey: string) {
const response = await fetch(
`https://api.whop.com/api/v5/memberships?license_key=${licenseKey}`,
{
headers: {
Authorization: `Bearer ${API_KEY}`,
},
}
);
const data = await response.json();
return data.data.length > 0 && data.data[0].valid;
}
async function cancelMembership(membershipId: string) {
const response = await fetch(`https://api.whop.com/api/v5/memberships/${membershipId}/cancel`, {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
});
return response.json();
}
async function createCheckout(planId: string, userId: string) {
const response = await fetch('https://api.whop.com/api/v5/checkout-configurations', {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
plan_id: planId,
success_url: 'https://yourapp.com/success',
cancel_url: 'https://yourapp.com/cancel',
metadata: {
user_id: userId,
},
}),
});
const checkout = await response.json();
return checkout.url; // Redirect user to this URL
}
async function getPayment(paymentId: string) {
const response = await fetch(`https://api.whop.com/api/v5/payments/${paymentId}`, {
headers: {
Authorization: `Bearer ${API_KEY}`,
},
});
return response.json();
}
async function refundPayment(paymentId: string, amount?: number) {
const response = await fetch(`https://api.whop.com/api/v5/payments/${paymentId}/refund`, {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount, // Optional: partial refund in cents
}),
});
return response.json();
}
async function createCourse(productId: string) {
const response = await fetch('https://api.whop.com/api/v5/courses', {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
product_id: productId,
title: 'Complete Web Development Course',
description: 'Learn fullstack development from scratch',
visibility: 'visible',
}),
});
return response.json();
}
async function createChapter(courseId: string) {
const response = await fetch('https://api.whop.com/api/v5/course-chapters', {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
course_id: courseId,
title: 'Introduction to JavaScript',
order: 1,
}),
});
return response.json();
}
async function createLesson(chapterId: string) {
const response = await fetch('https://api.whop.com/api/v5/course-lessons', {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
chapter_id: chapterId,
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,
}),
});
return response.json();
}
async function markLessonComplete(lessonId: string, userAccessToken: string) {
const response = await fetch(
`https://api.whop.com/api/v5/course-lessons/${lessonId}/mark-as-completed`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${userAccessToken}`,
},
}
);
return response.json();
}
async function createForumPost(
forumId: string,
title: string,
content: string,
userAccessToken: string
) {
const response = await fetch('https://api.whop.com/api/v5/forum-posts', {
method: 'POST',
headers: {
Authorization: `Bearer ${userAccessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
forum_id: forumId,
title,
content,
}),
});
return response.json();
}
async function sendChatMessage(channelId: string, content: string, userAccessToken: string) {
const response = await fetch('https://api.whop.com/api/v5/messages', {
method: 'POST',
headers: {
Authorization: `Bearer ${userAccessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
channel_id: channelId,
content,
}),
});
return response.json();
}
import { headers } from 'next/headers';
import crypto from 'crypto';
export async function POST(req: Request) {
const body = await req.text();
const signature = headers().get('x-whop-signature');
// Verify webhook signature
const webhookSecret = process.env.WHOP_WEBHOOK_SECRET!;
const hash = crypto.createHmac('sha256', webhookSecret).update(body).digest('hex');
if (hash !== signature) {
return new Response('Invalid signature', { status: 401 });
}
const event = JSON.parse(body);
// Handle webhook events
switch (event.action) {
case 'payment.succeeded':
await handlePaymentSuccess(event.data);
break;
case 'payment.failed':
await handlePaymentFailure(event.data);
break;
case 'membership.activated':
await handleMembershipActivated(event.data);
break;
case 'membership.deactivated':
await handleMembershipDeactivated(event.data);
break;
case 'dispute.created':
await handleDispute(event.data);
break;
default:
console.log(`Unhandled event: ${event.action}`);
}
return Response.json({ received: true });
}
Critical webhook events:
payment.succeeded - Payment completedpayment.failed - Payment failedpayment.pending - Payment pendingmembership.activated - Membership startedmembership.deactivated - Membership ended/cancelledinvoice.paid - Recurring payment succeededinvoice.past_due - Payment failed, retry in progressdispute.created - Customer disputed payment1. Redirect user 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();
2. Handle callback and exchange code:
export async function GET(req: Request) {
const url = new URL(req.url);
const code = url.searchParams.get('code');
// Exchange code for access token
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
await storeTokens(userId, access_token, refresh_token);
return Response.redirect('/dashboard');
}
async function sendNotification(userId: string, message: string) {
const response = await fetch('https://api.whop.com/api/v5/notifications', {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
user_id: userId,
title: 'New Update',
message,
type: 'info', // or 'success', 'warning', 'error'
}),
});
return response.json();
}
async function createPromoCode(productId: string, code: string, discount: number) {
const response = await fetch('https://api.whop.com/api/v5/promo-codes', {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
product_id: productId,
code,
discount_type: 'percentage',
discount_value: discount, // e.g., 20 for 20% off
max_uses: 100,
}),
});
return response.json();
}
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export async function middleware(request: NextRequest) {
const accessToken = request.cookies.get('whop_user_token')?.value;
if (!accessToken) {
return NextResponse.redirect(new URL('/login', request.url));
}
// Check if user has valid membership
const response = await fetch('https://api.whop.com/api/v5/me/memberships', {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
const memberships = await response.json();
const hasAccess = memberships.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*'],
};
// app/actions/whop.ts
'use server';
import { auth } from '@/lib/auth';
export async function createCheckoutSession(planId: string) {
const session = await auth();
if (!session?.user?.id) {
throw new Error('Unauthorized');
}
const response = await fetch('https://api.whop.com/api/v5/checkout-configurations', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WHOP_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
plan_id: planId,
success_url: `${process.env.NEXT_PUBLIC_URL}/dashboard?success=true`,
cancel_url: `${process.env.NEXT_PUBLIC_URL}/pricing?canceled=true`,
metadata: {
user_id: session.user.id,
},
}),
});
const data = await response.json();
return { url: data.url };
}
Whop provides a test/sandbox environment:
# Use ngrok or similar for local testing
ngrok http 3000
# Configure webhook URL in Whop Dashboard:
# https://your-ngrok-url.ngrok.io/api/webhooks
API Keys:
Webhooks:
Access Tokens:
Validation:
// lib/whop/check-access.ts
export async function checkMembershipAccess(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: {
Authorization: `Bearer ${process.env.WHOP_API_KEY}`,
},
}
);
const data = await response.json();
return data.data.length > 0;
}
// Use in API routes or Server Components
export async function GET(req: Request) {
const userId = await getCurrentUserId();
const hasAccess = await checkMembershipAccess(userId, 'prod_xxx');
if (!hasAccess) {
return Response.json({ error: 'No access' }, { status: 403 });
}
// Return protected content
return Response.json({ data: 'Premium content' });
}
// types/whop.ts
export interface WhopMembership {
id: string;
user_id: string;
product_id: string;
plan_id: string;
status: 'active' | 'paused' | 'canceled' | 'expired';
valid: boolean;
license_key: string;
quantity: number;
renews_at: string | null;
expires_at: string | null;
cancel_at_period_end: boolean;
created_at: string;
}
export interface WhopProduct {
id: string;
title: string;
description: string;
visibility: 'visible' | 'hidden';
created_at: string;
}
export interface WhopPayment {
id: string;
amount: number;
currency: string;
status: 'succeeded' | 'pending' | 'failed';
user_id: string;
product_id: string;
created_at: string;
}
export interface WhopWebhookEvent {
action: string;
data: any;
timestamp: string;
}
Need to find something specific?
Use Grep to search the 212 documentation files:
# Search all docs
grep -r "search term" .claude/skills/api/whop/docs/
# Search API references only
grep -r "memberships" .claude/skills/api/whop/docs/docs_whop_com/api-reference*
# List all endpoints
ls .claude/skills/api/whop/docs/docs_whop_com/api-reference*/
Common doc locations:
api-reference_*/developer_*/affiliates_*/Setup:
Core Features:
Advanced (if needed):
async function safeWhopRequest(url: string, options: RequestInit): Promise<any> {
try {
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();
} catch (error) {
console.error('Whop API error:', error);
throw error;
}
}
Common errors:
401 Unauthorized - Invalid or expired API key403 Forbidden - Insufficient permissions404 Not Found - Resource doesn't exist429 Too Many Requests - Rate limit exceeded500 Internal Server Error - Whop service issueWhop implements rate limiting. Best practices: