Early bird discounts live! Claim your offer

coupon.created

A new coupon code was authored in a project.

When this fires

A creator authored a new coupon code — in the dashboard, through the Telegram bot, over the REST API, or via the create_coupon MCP tool. All four routes emit the same event.

It fires on authoring, not on availability. A coupon created with a future starts_at fires this immediately and only becomes redeemable later, so do not read this as "a discount is now live" — check starts_at and active.

There is no separate event for a coupon becoming redeemable when its starts_at passes. Schedule against starts_at from this payload if you need that moment.

Required ability

project-coupon:view — token must carry this at mint time to subscribe an endpoint to this event.

Payload

{
  "id": "evt_01HX...",
  "type": "coupon.created",
  "created_at": "2026-11-20T09:14:02Z",
  "api_version": "2026-05-01",
  "project_id": "prj_01HX...",
  "data": {
    "coupon": {
      "id": "cpn_01HX...",
      "code": "BLACKFRIDAY",
      "name": "Black Friday 2026",
      "discount_type": "percentage",
      "discount_value": "25.0000",
      "currency_id": null,
      "duration": "once",
      "max_redemptions": 500,
      "max_redemptions_per_user": 1,
      "minimum_amount": null,
      "starts_at": "2026-11-27T00:00:00+00:00",
      "expires_at": "2026-12-01T00:00:00+00:00",
      "active": true
    }
  }
}

Field reference

FieldTypeNotes
idstring ULIDUnique event id, prefixed evt_. Use for idempotent processing.
typestringAlways coupon.created for this event.
created_atISO 8601 timestampServer-side emission time.
api_versionstringWebhook API contract version.
project_idstring ULIDProject the coupon belongs to.
data.coupon.idstring ULIDThe coupon, prefixed cpn_.
data.coupon.codestringThe redeemable string. Always uppercase — stored and compared that way.
data.coupon.namestringCreator-facing label. Never shown to subscribers.
data.coupon.discount_typepercentage | fixedWhether discount_value is a percent or an amount.
data.coupon.discount_valuedecimal stringFour decimal places, e.g. "25.0000". Parse as a decimal, never a float.
data.coupon.currency_idstring ULID | nullnull on a percentage coupon; set on a fixed-amount one.
data.coupon.durationonceWhich charges the discount applies to. Only once — the first payment — is authorable.
data.coupon.max_redemptionsinteger | nullTotal cap across everyone. null means unlimited.
data.coupon.max_redemptions_per_userintegerPer-subscriber cap. Always at least 1.
data.coupon.minimum_amountdecimal string | nullOrder floor in the plan's own currency. null means no floor.
data.coupon.starts_atISO 8601 | nullWhen it becomes redeemable. null means immediately.
data.coupon.expires_atISO 8601 | nullWhen it stops. null means it runs until switched off.
data.coupon.activebooleanWhether the creator has it switched on.

Caveats

  • The snapshot carries no redemption counts and no plan restriction. Counts move independently of the coupon row, and the plan list is a separate relation. Read both from GET /v1/projects/{project}/coupons/{coupon} if you need them.
  • A coupon can be created already switched off. active: false here is legitimate, not a race — a creator can author a code ahead of a campaign and switch it on later, which emits coupon.activated.
  • code is uppercase regardless of what was typed. A creator entering blackfriday gets BLACKFRIDAY. Compare case-sensitively against the uppercase form.
  • Timestamps inside data.coupon are ISO 8601 with offset (+00:00), unlike the envelope's created_at, which uses Z. Both are UTC.

How is this guide?

On this page

Subscriby is a product designed by you — for you.

No boardroom full of executives deciding what we ships next. Our roadmap always shaped by you with your feedback.

Share feedback or a request