Promo demo pass$40.00
Working withsave25sandbox

Pick a discount

$40.00 list
Show the call that made save25
await whop.promoCodes.create({
  "account_id": "biz_...",
  "code": "SAVE25",
  "promo_type": "percentage",
  "amount_off": 25,
  "base_currency": "usd",
  "new_users_only": false,
  "promo_duration_months": 1,
  "unlimited_stock": true,
  "one_per_customer": false,
  "product_id": "prod_..."
})
Whop refuses a create that says nothing about stock, so one of stock or unlimited_stock is always in the body, even though both are documented as optional.

What Whop hands back

percentage
You send25whole percent
Whop returns0.25decimal fraction

Print 0.25 straight from a list call and your page offers 0.25% off where you meant 25%. Send it back unchanged and the create is refused, because the write side floors at 1.

Duration
once
Uses left
unlimited
Per customer
no limit
Audience
everyone
Every read is the fraction: create, retrieve, list, and the checkout callback. Convert once, at the edge, and never store the result.// lib/promo.ts
promoType === "percentage" ? Math.round(raw * 100) : raw

Let someone use it

Open the checkout and type save25 into the promo row. Pay with 4242 4242 4242 4242.

What the checkout told us

Apply a code inside the checkout and onPromoCodeChanged fires. Whatever it hands over lands here, unedited.

The callback also fires with null when the buyer removes the code, so read it as the current state and not as a one-time event.

Put it in a link

https://nextjs-whop-promo-codes-demo.vercel.app/?promo=save25
$40.00$30.00
what they see first
// the whole feature
<WhopCheckoutEmbed
planId={planId}
promoCode="save25"
/>
A code that no longer works does not raise an error here. The checkout opens at full price with an empty promo row and the callback never fires, so check the code before you send people to it.

What the receipt says

Buy something to prove it

Until the server reads the payment back, a discount is only a number on a screen.

This product lists at $40.00. The payment itself records the price before the discount, what was charged, and which code did it.

Your codes

3 on Whop
  • active0 used
  • active0 used
  • active0 used
Pause and resume are reversible, so they are safe to hand a stranger. Archiving is not, so this demo leaves it out. Pause calls promoCodes.deactivate and resume calls promoCodes.activate.
Activity loglive

Everything the page does behind the scenes shows up here.