Version
WEBHOOK

coupon.exhausted

The last remaining use of a capped coupon was taken.

When this fires

A settled redemption took the last remaining use of a capped coupon. Fires immediately after the coupon.redeemed that consumed it, in the same emission.

Use it to close a campaign automatically: pull the code out of a landing page, stop an ad, post "sold out", or notify the creator that the cap they set has been reached.

Only capped coupons can fire this. A coupon with max_redemptions: null is unlimited and never emits this event.

Exhaustion counts live reservations, so it can un-exhaust

This is the one behaviour that surprises people. The quota is enforced against settled redemptions plus reservations whose hold has not expired, so a coupon can report exhausted while the last few uses are still only held by checkouts in progress.

If one of those checkouts is abandoned, the hold expires and its use returns to the pool. The coupon becomes redeemable again, and no event fires for that, because nothing changed on the coupon row.

That design is deliberate: it means an abandoned checkout never permanently burns a use, and no sweeper job is needed to reclaim one. The consequence for you is that coupon.exhausted is a strong signal but not a permanent state. If your automation does something hard to undo (pausing an ad spend, emailing a list) verify the current state with the coupon endpoint first, and read redemptions.remaining rather than comparing counts yourself.

redemptions_count can exceed max_redemptions. The counter is bumped when a checkout takes a hold, not when it settles, and is only decremented when a hold is explicitly released. Lapsed holds the stale-reservation sweep has not yet reclaimed still sit in it, so on this event redemptions_count is equal to or greater than max_redemptions, never below it. Read redemptions.remaining from the API if you need the reservation-aware figure.

Caveats

  • It can fire more than once for the same coupon. Exhausted → a hold expires → redeemable → exhausted again is a legitimate sequence. De-dup on the event id, not on the coupon id.
  • A creator raising max_redemptions does not emit a matching "un-exhausted" event. That is a coupon.updated with max_redemptions in its changes.
  • Expiry is a different thing entirely. A coupon that runs past expires_at stops applying without ever being exhausted, and emits nothing.

Related events

  • coupon.redeemed: the redemption that consumed the last use; always precedes this.
  • coupon.updated: where a raised cap shows up.
  • coupon.deactivated: switched off by decision rather than run out by use.

Ability to subscribe

A token needs this to subscribe an endpoint to the event.

Header Parameters

SB-Signature*string

t=<unix seconds>,v1=<hex>: the HMAC-SHA256 of "<t>.<raw body>" under the endpoint's secret. Verify it before acting, and refuse a t more than 300 seconds from now. During a secret rotation a v0= signature under the previous secret may precede v1=.

SB-Event-Id*string

The event's ULID, bare. The envelope's id is the same ULID prefixed evt_, so strip the prefix before comparing. Deduplicate on it: a retry carries the same id.

SB-Event-Name*string

The event name, the same as the envelope's type.

Content-Type*string

Always application/json.

User-Agent*string

Always Subscriby-Webhooks/1.0.

Payload

JSONWhat Subscriby posts to your endpoint

The signed JSON envelope posted to your endpoint.

The envelope every event is delivered in.

Responses

2XXAny success status

Your endpoint acknowledged the delivery. Any 2xx status within 30 seconds marks it delivered; the response body is ignored.

defaultAny other status

Any other status, a connection failure, or no answer within 30 seconds counts as a failed attempt. The delivery is retried 8 times, after 10 seconds, 30 seconds, 2 minutes, 10 minutes, 1 hour, 6 hours, 1 day, 3 days; the last failure dead-letters it, and it can be retried from the dashboard or POST /v1/webhook-deliveries/{delivery}/retry. After 20 consecutive failures the endpoint is paused until it is resumed.

How is this guide?

Last updated on