Getting Started

  1. Head to whop.com/dashboard
  2. Create a new app in your organization
  3. Note down your API credentials from the app details page

Set Up Your Next.js Project

  1. Create a new Next.js project with TypeScript and Tailwind CSS:
pnpm create next-app my-whop-app --typescript --tailwind --eslint
cd my-whop-app
  1. Install the required dependencies:
pnpm add @whop/api @whop-apps/dev-proxy

Configure Environment Variables

Create a .env.local file with the following variables:

# Required: Your app's API key from the Whop dashboard
WHOP_API_KEY=your_api_key_here

# Required: Your app's ID from the Whop dashboard
WHOP_APP_ID=your_app_id_here

# Required: The user ID of your app's agent user
# This user will make API calls on behalf of your app
WHOP_AGENT_USER_ID=your_agent_user_id

# Optional: Secret for validating webhooks
# Only needed if you're implementing webhook functionality
WHOP_WEBHOOK_SECRET=your_webhook_secret

Setting Up the Whop Integration

In your Whop dashboard, set up your app’s configuration:

  • Set the “Base Domain” to your production domain (e.g., https://your-app.com)
  • Configure the “App Path” (e.g., /experience/[experienceId])
  • Enable localhost mode for development

Initialize the Whop API Client

Create a new file lib/whop-api.ts:

"use server"; // ensure that this file is not imported by the client bundle

import { WhopApi, makeUserTokenVerifier } from "@whop/api";

// Initialize the Whop API client with your app's credentials
export const whopApi = WhopApi({
  // Your app's API key - required for all API calls
  appApiKey: process.env.WHOP_API_KEY ?? "fallback",

  // The user ID that will make API calls on behalf of your app
  // This is typically your app's agent user
  onBehalfOfUserId: process.env.WHOP_AGENT_USER_ID,
});

// Create a function to verify user tokens
// This is used to authenticate requests from the Whop iframe
export const verifyUserToken = makeUserTokenVerifier({
  appId: process.env.WHOP_APP_ID ?? "fallback",
  dontThrow: true, // Return null instead of throwing on invalid tokens
});

Set Up Development Environment

  1. Add the dev proxy script to your package.json:
{
  "scripts": {
    "dev": "next dev",
    "whop-proxy": "whop-proxy"
  }
}
  1. Run the development environment:

The whop-proxy will by default run the npm dev script with a custom PORT environment variable. Ensure your app correctly responds to this env var.

# Run the proxy
pnpm whop-proxy

If you need to customise this behaviour, you can use the following options:

  • proxyPort: which port the proxy should start on
  • upstreamPort: which port the upstream server should listen on
  • npmCommand: override which npm command to run. Can be "dev", "start", etc..
  • command: provide a custom shell command to run instead of pnpm dev

For example:

pnpm whop-proxy --proxyPort=3000 --upstreamPort=3001 --command='echo "Hello from the proxy running on $PORT!"'

Authenticating Users

Get user ID information anywhere you want.

// app/components/SomeComponentThatNeedsAuthVerified.tsx
import { verifyUserToken } from "@/lib/whop-api";
import { headers } from "next/headers";

export async function Page() {
  const requestHeaders = await headers();
  const userTokenData = await verifyUserToken(requestHeaders);

  if (!userTokenData) {
    return (
      <div className="p-4 bg-red-50 border border-red-200 rounded-lg">
        <h2 className="text-red-600 font-semibold">Authentication Error</h2>
        <p className="text-red-500">Invalid or missing user token</p>
      </div>
    );
  }

  return (
    <div className="p-4 bg-green-50 border border-green-200 rounded-lg">
      <h2 className="text-green-600 font-semibold">
        Authentication Successful
      </h2>
      <p>
        Authenticated as:{" "}
        <code className="bg-gray-100 px-2 py-1 rounded">
          {userTokenData.userId}
        </code>
      </p>
    </div>
  );
}

Making API Requests

Here are examples of direct SDK usage:

// Get user profile
const userProfile = await whopApi.PublicUser({
  userId: "user_id_here",
});

// List experiences
const experiences = await whopApi.ListExperiences({
  companyId: "company_id_here",
  first: 10, // Optional: limit results
  after: "cursor_here", // Optional: pagination
  accessPassId: "access_pass_id_here", // Optional: filter by access pass
  appId: "app_id_here", // Optional: filter by app
  onAccessPass: true, // Optional: only show experiences on access pass
});

// Send messages
// Direct message
await whopApi.SendDmMessage({
  toUserId: "target_user_id",
  message: "Hello!",
});

// Chat message
await whopApi.SendChatMessage({
  experienceId: "target_experience_id",
  message: "Hello everyone!",
});

// Feed message
await whopApi.SendFeedMessage({
  experienceId: "target_experience_id",
  message: "New announcement!",
});

There are many more (and growing) API methods that are all typed and ready to use.

Charge user

If you want to charge the user custom amounts, follow this payment processing flow.

// app/lib/payments.ts
import { whopApi } from "./whop-api";

export async function chargeUser(options: PaymentOptions): string | null {
  try {
    const chargeResult = await whopApi.ChargeUser({
      input: {
        userId: options.userId,
        amount: options.amount,
        currency: options.currency,
        description: options.description, // Show a custom message to the user at checkout.
        metadata: options.metadata, // pass any Record<string, any>. This data will be sent in the payment
        redirectUrl: options.redirectUrl, // optional, redirect the user to a custom url AFTER checkout
        affiliateCode: options.affiliateCode, // attribute this purchase to a custom username / user_id
      },
    });

    if (!chargeResult.chargeUser) {
      throw Error("Failed to charge user.");
    }

    // If we get back a checkout session, we need to return the `purchaseUrl` to the client application.
    // The client should redirect or open this URL.
    if (chargeResult.checkoutSession) {
      return chargeResult.chargeUser.checkoutSession.purchaseUrl;
    }

    // Otherwise the checkout processing succeeded, and the payment webhook will be received soon.
    return null;
  } catch (error) {
    console.error("Payment processing failed:", error);
    throw new Error("Failed to process payment");
  }
}

Upon payment completion by the client, your will receive a webhook. (Only if you configured webhooks in the dashboard).

Set Up Payment Received Webhook

Create a new API route in app/api/webhooks/whop/route.ts:

import { waitUntil } from "@vercel/functions";
import { makeWebhookValidator } from "@whop/api";
import type { NextRequest } from "next/server";

// Initialize webhook validator with your webhook secret
const validateWebhook = makeWebhookValidator({
  webhookSecret: process.env.WHOP_WEBHOOK_SECRET ?? "fallback",
});

// Handle webhook events
export async function POST(request: NextRequest): Promise<Response> {
  try {
    // Validate the webhook signature
    const webhookData = await validateWebhook(request);

    // Handle different webhook events
    switch (webhookData.action) {
      case "payment.succeeded":
        const { id, final_amount, amount_after_fees, currency, user_id } =
          webhookData.data;

        // Process the successful payment
        waitUntil(
          handlePaymentSuccess(
            user_id,
            final_amount,
            currency,
            amount_after_fees
          )
        );
        break;

      case "payment.failed":
        const { error, user_id: failedUserId } = webhookData.data;
        waitUntil(handlePaymentFailure(failedUserId, error));
        break;

      // Add more webhook event handlers as needed
    }

    return new Response("OK", { status: 200 });
  } catch (error) {
    console.error("Webhook processing failed:", error);
    return new Response("Webhook validation failed", { status: 400 });
  }
}

// Handle successful payments
async function handlePaymentSuccess(
  userId: string,
  amount: number,
  currency: string,
  amountAfterFees: number
) {
  try {
    // Update your database
    // Grant access to premium features
    // Send confirmation email
    // etc.
  } catch (error) {
    console.error("Failed to process successful payment:", error);
  }
}

// Handle failed payments
async function handlePaymentFailure(userId: string, error: any) {
  try {
    // Update your database
    // Notify the user
    // etc.
  } catch (error) {
    console.error("Failed to process payment failure:", error);
  }
}

Deployment

  1. Push your code to GitHub
  2. Set up your environment variables in your hosting platform
  3. Update your Whop dashboard settings:
    • Verify your Base Domain
    • Update webhook callback URLs
    • Test the production integration

Need Help?