Stripe coupons vs promotion codes (what founders mix up)
In Stripe, the thing people type at checkout is a promotion code. The thing that actually defines the discount is a coupon. Founders launching on Product Hunt mix these up constantly: they invent a string like LAUNCH50, paste it in a thread, and never create the objects in Stripe. Checkout then does exactly what you told it to do. Nothing.
The one-sentence difference
- Coupon: backend discount object. You decide who gets it and when. Apply it from your Dashboard or API.
- Promotion code: customer-facing code that wraps a coupon. Many codes can map to one coupon (
FALLPROMOandSPRINGPROMOboth pointing at the same 25% off).
If you only create a coupon, customers have nothing to type unless you also create a promotion code (or you apply the coupon yourself on their subscription). If you only write a string in Notion, Stripe has never heard of it.
Duration: once, repeating, forever
Coupon duration controls how long the discount sticks after redemption:
once: first invoice onlyrepeating: plusduration_in_monthsfor a fixed windowforever: every invoice until you remove it
This is the detail launch threads skip. "50% off" without a duration is three different products: first invoice only, three months, or forever.
Once coupons have a gotcha. After the invoice that consumed them finalizes, Stripe removes that discount from the subscription's discounts array. The invoice still shows the discount. The subscription can look like it has no discount even though the coupon already applied. If you're debugging "did LAUNCH50 work?", check the invoice, not only the live subscription object.
What promotion codes add that coupons alone don't
Stripe's comparison table is blunt. Promotion codes support:
- Restrict to a specific customer
- First-time purchase only
- Minimum spend to redeem
Coupons alone do not. Those controls live on the promotion code (distribution layer), not on the coupon (discount logic). Promo codes also inherit product restrictions from their parent coupon.
If you need "new customers only, $50 minimum, code expires Friday," that is promotion code configuration on top of a coupon with the right percent or amount off.
Redemption limits are shared
max_redemptions on the coupon counts both direct coupon applications and promotion code redemptions. Fifty means fifty total uses across every path, not fifty per code. When you create several promotion codes for one coupon, they share that budget unless you also set tighter limits on each code (and a code's limit cannot exceed the coupon's).
Same idea for expiry: a promotion code's expires_at cannot be later than the coupon's redeem_by when the coupon has one.
Stacking and order
You can put up to 20 discounts on a subscription, subscription item, or invoice. Order matters when you mix amount_off and percent_off. Twenty percent then $5 off is not the same invoice as $5 off then twenty percent. You also cannot pass a coupon and a promotion code created from that same coupon together in one discounts list.
Delete vs archive
- Delete a coupon: stops new applications. Existing discounts on subscriptions or invoices stay. Associated promotion codes get archived.
- Promotion codes: toggle
active(or archive in the Dashboard) without deleting the parent coupon.
So "turn off the Product Hunt code" usually means archive the promotion code, not delete the coupon you still use for sales-assisted deals.
What this means for a launch listing
For a Dealpuffin-style listing (or any directory that syncs from your billing provider), you need a real coupon and usually a real promotion code in Stripe, Paddle, or Polar: duration, cap, expiry. A spreadsheet note is not a redeemable object. Connect Stripe is the setup path; what can be listed and caps and expiry explain what we read from the provider. Seekers use find a code against those live objects, not against screenshots. For how to read a public discount page before you paste anything, see How to read a SaaS discount page.
Practical checklist before you tweet a code
- Create the coupon with percent or amount off, duration, and optional
max_redemptions/redeem_by. - Create the promotion code string customers will type; set first-time, minimum spend, customer, or code-level expiry if you need them.
- Test redeem in a Checkout session with
allow_promotion_codes(or apply via API) on a throwaway customer. - Confirm the invoice line, then confirm what the subscription
discountsarray looks like after aoncecoupon finalizes. - Only then put the string on Product Hunt, your README, or a listing.
Related on Dealpuffin
Provider setup and listing rules: Connect Stripe, Caps and expiry, What can be listed. For readers evaluating a code: How to read a SaaS discount page.