Commet
  • Pricing
Log InTry out
Introduction

Subscription Events

subscription.createdsubscription.activatedsubscription.canceledsubscription.updatedsubscription.plan_changedsubscription.cancellation_scheduledsubscription.cancellation_revokedsubscription.plan_change_scheduledsubscription.plan_change_revokedsubscription.past_due

Trial Events

trial.startedtrial.convertedtrial.expiredtrial.will_endtrial.checkout_ready

Checkout Events

checkout.ready

Payment Events

payment.receivedpayment.failedpayment.recoveredpayment.refundedpayment.disputedpayment.dispute_resolved

Invoice Events

invoice.createdinvoice.upcominginvoice.overdueinvoice.voided

Payment Method Events

payment_method.attachedpayment_method.updated

Customer Events

customer.createdcustomer.updatedcustomer.state_changed

Credits & Balance Events

credits.grantedcredits.purchasedcredits.lowcredits.depletedcredits.expiredbalance.topped_upbalance.lowbalance.depleted

Quota & Usage Events

quota.threshold_reachedquota.exceededusage.recorded

Seat Events

seats.updatedseats.limit_reached

Add-on Events

addon.activatedaddon.deactivated

Payout Events

payout.availablepayout.createdpayout.paidpayout.failed
DocumentationKnowledge BaseBuild with AIAPI ReferenceWebhooks

customer.state_changed

Aggregate entitlement event — what can this customer access right now.

Payload

All webhook payloads follow a consistent top-level structure with event-specific data nested within the data object.

customerIdstring

The customer ID. Returns your externalId if you provided one when creating the customer, otherwise returns the Commet publicId.

triggerstring

What caused the transition. One of: subscription_created, subscription_activated, subscription_canceled, plan_change, past_due, trial_started, trial_converted, trial_expired, cancellation_scheduled, cancellation_revoked, seats_updated, addon_activated, addon_deactivated, credits_depleted, balance_depleted, quota_exceeded.

statusstring

The customer's current subscription status, or "none" when no live subscription exists. Grant access only when trialing or active.

subscriptionIdstring | null

The live subscription ID, or null when status is none.

planobject | null

The current plan (id and name), or null when status is none.

billingIntervalstring | null

The current billing interval.

consumptionModelstring | null

The plan's consumption model: metered, credits, or balance.

featuresarray

Current feature access, one entry per plan feature: code, name, type, allowed, enabled, current, included, remaining, overageQuantity, overageUnitPrice, unlimited, overageEnabled, billedQuantity. Fields that do not apply to a feature type are null.

seatsarray

Summary of seats-type features: code, current, included, remaining, unlimited.

creditsobject | null

For credits plans: planCredits, purchasedCredits, totalCredits. Null otherwise.

balanceobject | null

For balance plans: currentBalance in rate scale (10000 = $1.00). Null otherwise.

{
  "event": "customer.state_changed",
  "timestamp": "2026-03-25T14:32:00.000Z",
  "organizationId": "org_abc123",
  "mode": "live",
  "apiVersion": "2026-05-25",
  "data": {
    "customerId": "user_123",
    "trigger": "subscription_activated",
    "status": "active",
    "subscriptionId": "sub_1a2b3c4d",
    "plan": {
      "id": "plan_pro_monthly",
      "name": "Pro"
    },
    "billingInterval": "monthly",
    "consumptionModel": "metered",
    "features": [
      {
        "code": "api_calls",
        "name": "API Calls",
        "type": "usage",
        "allowed": true,
        "enabled": null,
        "current": 120,
        "included": 1000,
        "remaining": 880,
        "overageQuantity": 0,
        "overageUnitPrice": 50,
        "unlimited": false,
        "overageEnabled": true,
        "billedQuantity": null
      },
      {
        "code": "editors",
        "name": "Editors",
        "type": "seats",
        "allowed": true,
        "enabled": null,
        "current": 3,
        "included": 5,
        "remaining": 2,
        "overageQuantity": 0,
        "overageUnitPrice": null,
        "unlimited": false,
        "overageEnabled": false,
        "billedQuantity": null
      }
    ],
    "seats": [
      {
        "code": "editors",
        "current": 3,
        "included": 5,
        "remaining": 2,
        "unlimited": false
      }
    ],
    "credits": null,
    "balance": null
  }
}

One event to sync access

Instead of handling every lifecycle event (subscription.activated, subscription.canceled, trial.expired, ...) to keep your access flags in sync, handle this single event. Every entitlement transition fires it with the customer's current state, computed at delivery time:

triggerWhen
subscription_createdA subscription was created (status pending_payment — no access yet).
subscription_activatedA payment confirmed the subscription.
trial_startedA trial began.
trial_convertedA trialing customer converted to paid via plan change.
trial_expiredA trial ran out and regular billing began.
plan_changeA plan change executed (immediate or scheduled).
cancellation_scheduledA cancellation was scheduled — access continues until period end.
cancellation_revokedA scheduled cancellation was reverted.
subscription_canceledThe subscription terminated — status becomes none.
past_dueA recurring payment failed — access is cut immediately.

Handling the payload

Use status as the access gate (trialing and active grant access), features for per-feature limits, and credits/balance for consumption headroom on credits/balance plans. The payload reflects the state at delivery time — if two transitions happen back to back, the later event always carries the final state, so processing events in timestamp order converges to the correct result.

How is this guide?

customer.updated

Fired when a customer's details change. Carries the full current customer resource.

credits.granted

Non-purchase credits were granted to a subscription.