crxpaydocs

Coupons

Create discount codes for win-back campaigns, launch offers, and referrals. Mirrored to Stripe automatically.

Coupons are codes your users redeem at checkout. Each code is stored on crxpay and mirrored to your connected Stripe account the moment you create it, so there's no extra round-trip at purchase time.

Create a coupon

Open Coupons in the dashboard sidebar and click New coupon.

FieldNotes
CodeUppercase letters, digits, _ or -. Shown to the user.
TypePercentage off, Fixed amount off (cents), or Free first N months
Amount1–100 for percentage, cents for fixed, 1–36 for months
Repeat forHow many months the discount repeats. Blank means one-time. Ignored for first_n_months.
Max redemptionsOptional cap. After this many uses, the code stops working.
ExpiresOptional cut-off date.

On create, crxpay calls Stripe's Coupons API on your connected account and stores the resulting coupon ID. If the Stripe call fails, the local coupon is not created — you'll see the Stripe error and can retry.

Redeem a coupon from the SDK

Pass the code to showPaywall or openCheckout:

await crxpay.showPaywall({ plan: 'pro_monthly', coupon: 'WINBACK30' });

The paywall UI should allow the user to enter their own code as well. Validate a user-entered code before showing it as applied:

// Inside your paywall UI
const res = await fetch('https://api.crxpay.io/v1/sdk/coupons/validate', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${publicApiKey}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ code: userInput }),
});
const data = await res.json();
// data.code, data.type, data.amount, data.durationMonths, data.expiresAt

If the code is archived, expired, or out of redemptions, the endpoint returns 404 invalid_or_expired.

Detect an active discount

When a subscription has a coupon applied, the SDK exposes it on sub.discount:

const sub = await crxpay.ready();
if (sub.discount) {
  // { code: 'WINBACK30', type: 'percentage', amount: 30, endsAt: Date | null }
  showBanner(`Your ${sub.discount.code} discount runs until ${sub.discount.endsAt}`);
}

endsAt is null for coupons that last the lifetime of the subscription.

Webhooks

When a user completes checkout with a valid code, you receive a coupon.redeemed webhook:

{
  "type": "coupon.redeemed",
  "data": {
    "mode": "live",
    "code": "WINBACK30",
    "type": "percentage",
    "amount": 30,
    "processorSubscriptionId": "sub_1Q..."
  }
}

Redemptions are deduplicated per (coupon, subscription) — even if Stripe re-delivers the customer.subscription.created event, you'll only see one coupon.redeemed webhook per subscription.

Win-back flow

  1. A user cancels. You receive subscription.cancelled.
  2. Send them an email (or show an in-extension banner on next open) with a code like COMEBACK40.
  3. They click → crxpay.showPaywall({ coupon: 'COMEBACK40' }).
  4. They check out. You receive coupon.redeemed and subscription.created.

The Coupons dashboard page shows redemption counts per code so you can measure each campaign.

Was this page helpful?

Your feedback shapes what we document next.