Event reference
Six event types. All share the common envelope (id, created, version, event, tenant_id) and the event-specific fields listed below.
Defaults: which events fire?
Section titled “Defaults: which events fire?”| Event | Default | Why |
|---|---|---|
license.created | always | Foundational. Cannot be disabled. |
license.activated | opt-in | Fires on every new device activation — can be high-volume. |
license.deactivated | opt-in | Fires on every device release — can be high-volume. |
license.expired | on | Fires when a buyer’s key is detected as expired during activate/validate. Mute by setting licenseWebhookEvents.expired = false. |
license.refunded | on | Stripe refund detected. Keylight has already auto-revoked the license. |
subscription.renewed | on | Subscription invoice paid (recurring billing only). |
The two opt-in events have explicit toggles in Dashboard → Settings → Integrations → Webhook events.
license.created
Section titled “license.created”Fires when a customer’s checkout session completes successfully and Keylight has minted a new license key.
When: Stripe Connect checkout.session.completed fires; license row is persisted.
{ "id": "5d3c7e02-2b78-4f1a-b3a4-7d2f6c2e8b9d", "created": 1714669200, "version": "2026-05-01", "event": "license.created", "tenant_id": "your-account", "key": "ACME-4B2K-9XJP-T8WM-Q3VZ", "product_id": "pro", "key_type_id": "lifetime", "customer_email": "buyer@example.com", "session_id": "cs_live_a1b2c3..."}Use this to email the key from your own template, provision the buyer’s account, or kick off post-purchase automations.
license.activated
Section titled “license.activated”Fires when a buyer successfully activates the key on a new device (instance). Not fired on re-activations of an already-bound device.
Default: off. Enable in Settings → Integrations → Webhook events → license.activated.
{ "id": "…", "created": 1714669300, "version": "2026-05-01", "event": "license.activated", "tenant_id": "your-account", "key": "ACME-****-****-****-Q3VZ", "product_id": "pro", "key_type_id": "lifetime", "instance_id": "8f3a7c10-…", "instance_name": "Nicolas' MacBook Pro"}license.deactivated
Section titled “license.deactivated”Fires when a buyer releases a device slot (uninstalls / signs out / clicks “deactivate” in your app).
Default: off.
{ "id": "…", "created": 1714669400, "version": "2026-05-01", "event": "license.deactivated", "tenant_id": "your-account", "key": "ACME-****-****-****-Q3VZ", "product_id": "pro", "key_type_id": "lifetime", "instance_id": "8f3a7c10-…"}license.expired
Section titled “license.expired”Fires the first time a buyer’s expired key is detected during a /validate or /activate call. Useful for prompting renewal flows in your app or your CRM.
Default: on.
{ "id": "…", "created": 1714669500, "version": "2026-05-01", "event": "license.expired", "tenant_id": "your-account", "key": "ACME-****-****-****-Q3VZ", "product_id": "pro", "key_type_id": "trial", "expired_at": 1714665600}license.refunded
Section titled “license.refunded”Fires when Stripe reports the underlying charge has been refunded. Keylight automatically:
- Revokes the license (status flips to
revoked; subsequent activations are rejected). - Writes a negative payment record so revenue dashboards reflect the refund.
The event lets you mirror the action on your side — cancel the buyer’s account, send a confirmation, etc.
Default: on.
{ "id": "…", "created": 1714669600, "version": "2026-05-01", "event": "license.refunded", "tenant_id": "your-account", "key": "ACME-****-****-****-Q3VZ", "product_id": "pro", "key_type_id": "lifetime", "session_id": "cs_live_a1b2c3...", "charge_id": "ch_3O…", "amount_refunded": 4900, "currency": "usd"}amount_refunded is in minor units (cents for USD). For partial refunds, only this amount is negative in revenue reports — the license is still revoked because Stripe considers it refunded.
subscription.renewed
Section titled “subscription.renewed”Fires when a recurring subscription cycle bills successfully (Stripe invoice.payment_succeeded with billing_reason: subscription_cycle).
Default: on.
{ "id": "…", "created": 1714669700, "version": "2026-05-01", "event": "subscription.renewed", "tenant_id": "your-account", "invoice_id": "in_1O…", "subscription_id": "sub_1O…", "stripe_customer_id": "cus_O…", "amount_paid": 1900, "currency": "usd", "period_end": 1717348000}Versioning
Section titled “Versioning”The version field on every envelope is a date string. Today: 2026-05-01.
- Adding optional fields to an event does not change the version.
- Removing fields or changing semantics would bump the version. We’ll publish a migration note ahead of any bump.
- New event types may be added without bumping (your handler just ignores unknown
eventvalues).
- Always route on
event.event, not the URL or the order of keys. - Always dedupe on
event.id— the sameidmay arrive more than once. See retries & idempotency. - Always verify the signature before parsing the body. See verify the signature.
- Return 2xx fast. Heavy lifting (emailing, provisioning) belongs on a background queue on your side, not in the webhook handler. A slow handler trips retries.