Skip to main content

Trends, tech news, and tool comparisons.

Stripe coupons vs promotion codes (what founders mix up)

· 5 min read
Dealpuffin
Proof points and live coupons for early adopters

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​

Per Stripe's coupons docs:

  • 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 (FALLPROMO and SPRINGPROMO both 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 only
  • repeating: plus duration_in_months for a fixed window
  • forever: 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​

  1. Create the coupon with percent or amount off, duration, and optional max_redemptions / redeem_by.
  2. Create the promotion code string customers will type; set first-time, minimum spend, customer, or code-level expiry if you need them.
  3. Test redeem in a Checkout session with allow_promotion_codes (or apply via API) on a throwaway customer.
  4. Confirm the invoice line, then confirm what the subscription discounts array looks like after a once coupon finalizes.
  5. Only then put the string on Product Hunt, your README, or a listing.

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.