customer.state_changed
Evento agregado de permisos: a qué puede acceder este cliente en este momento.
Payload
All webhook payloads follow a consistent top-level structure with event-specific data nested within the data object.
The customer ID. Returns your externalId if you provided one when creating the customer, otherwise returns the Commet publicId.
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.
The customer's current subscription status, or "none" when no live subscription exists. Access is granted while trialing, active, or past_due — past_due is a permissive grace window during dunning.
The live subscription ID, or null when status is none.
The current plan (id and name), or null when status is none.
The current billing interval.
The plan's consumption model: metered, credits, or balance.
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.
Summary of seats-type features: code, current, included, remaining, unlimited.
For credits plans: planCredits, purchasedCredits, totalCredits. Null otherwise.
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
}
}Un solo evento para sincronizar el acceso
En lugar de gestionar cada evento del ciclo de vida (subscription.activated, subscription.canceled, trial.expired, ...) para mantener tus flags de acceso sincronizados, gestioná este único evento. Cada transición de permisos lo dispara con el estado actual del cliente, calculado en el momento de la entrega:
| trigger | Cuándo |
|---|---|
subscription_created | Se creó una suscripción (estado pending_payment: todavía sin acceso). |
subscription_activated | Un pago confirmó la suscripción. |
trial_started | Comenzó una prueba. |
trial_converted | Un cliente en prueba se convirtió a pago mediante un cambio de plan. |
trial_expired | Una prueba se agotó y comenzó la facturación regular. |
plan_change | Se ejecutó un cambio de plan (inmediato o programado). |
cancellation_scheduled | Se programó una cancelación: el acceso continúa hasta el fin del período. |
cancellation_revoked | Se revirtió una cancelación programada. |
subscription_canceled | La suscripción terminó: status pasa a none. |
past_due | Un pago recurrente falló: el acceso se corta de inmediato. |
Cómo manejar el payload
Usá status como la barrera de acceso (trialing y active otorgan acceso), features para los límites por feature, y credits/balance para el margen de consumo en planes de créditos/balance. El payload refleja el estado en el momento de la entrega: si dos transiciones ocurren una tras otra, el evento posterior siempre lleva el estado final, así que procesar los eventos en orden de timestamp converge al resultado correcto.
¿Cómo está esta guía?