DOCS / RUNBOOKS / REISSUE STRIPE COUPON
VIEW RAW

Reissue a Stripe coupon (replace code or expiry)

Stripe promotion codes are immutable for the code string and the expires_at timestamp after creation. To change either, you deactivate the existing one and create a new one pointing to the same underlying coupon (which is mutable for name and metadata only).

When to use this

  • Spam-filter rejection on the existing code (e.g. AGENCY3FREEAGENCY3 because "FREE" in caps trips SpamAssassin).
  • Marketing wants to extend or shorten the validity window (expires_at).
  • Campaign attribution: a single coupon (the discount rule) backing multiple promotion codes for different channels (LinkedIn vs Slack vs partner site), each with its own metadata.campaign.

Underlying model

Stripe has two separate concepts:

ObjectMutable fieldsNotes
Couponname, metadata, currency_optionsThe discount rule (percent_off, duration, applies_to). Created once, reused forever.
Promotion codeactive, metadata, customerThe customer-facing string + restrictions. Wraps a coupon.

So you create the coupon once, then create N promotion codes pointing to it.

Steps to swap a code

# 1. Deactivate the existing promotion code
curl -X POST "https://api.stripe.com/v1/promotion_codes/$OLD_PROMO_ID" \
  -u "$STRIPE_SECRET_KEY:" \
  -d "active=false"

# 2. Create the new promotion code, same coupon, new code/expiry
curl -X POST "https://api.stripe.com/v1/promotion_codes" \
  -u "$STRIPE_SECRET_KEY:" \
  -d "promotion[type]=coupon" \
  -d "promotion[coupon]=$COUPON_ID" \
  -d "code=AGENCY3" \
  -d "expires_at=1780358400" \
  -d "metadata[campaign]=agency_launch_2026_05" \
  -d "metadata[replaces]=AGENCY3FREE_spamfilter"

The MCP shortcut for the second call:

mcp__plugin_stripe_stripe__stripe_api_execute({
  stripe_api_operation_id: "PostPromotionCodes",
  parameters: {
    "promotion.type": "coupon",
    "promotion.coupon": "<coupon_id>",
    code: "AGENCY3",
    expires_at: 1780358400,
    "metadata.campaign": "agency_launch_2026_05",
  },
});

Always-do checks

  1. expand[]=applies_to when reading the coupon — Stripe hides the applies_to.products field by default, so a default GET /coupons/X looks like the coupon discounts everything, when in fact it's correctly scoped. Always pass expand[]=applies_to when verifying scope:

    curl "https://api.stripe.com/v1/coupons/$COUPON_ID?expand[]=applies_to" -u "$STRIPE_SECRET_KEY:"
    
  2. Check times_redeemed before deactivating the old code. Existing redemptions keep their discount per Stripe's rules even after deactivation, but counting how many is useful for campaign reporting (metadata.campaign lookup later).

  3. Communicate the rotation to anyone running outreach copy. Old codes silently fail with "Promo code is no longer active" — your sales team should not be quoting AGENCY3FREE after AGENCY3 is live.

Spam-filter cheat sheet

Avoid in promo codes:

  • FREE in any case (high SpamAssassin score)
  • WIN, PRIZE, 100%OFF
  • All-caps long strings (use 6-12 chars, mixed letters + numbers)

Generally safe patterns: AGENCY3, EARLYBIRD, LAUNCH99, Q2-PARTNER. Stripe accepts a-z, A-Z, 0-9, dashes; case-insensitive uniqueness is enforced across active codes only.


Found a typo or want to suggest an edit? Email support@triadagency.ai.