Skip to content

Event reference

Six event types. All share the common envelope (id, created, version, event, tenant_id) and the event-specific fields listed below.

EventDefaultWhy
license.createdalwaysFoundational. Cannot be disabled.
license.activatedopt-inFires on every new device activation — can be high-volume.
license.deactivatedopt-inFires on every device release — can be high-volume.
license.expiredonFires when a buyer’s key is detected as expired during activate/validate. Mute by setting licenseWebhookEvents.expired = false.
license.refundedonStripe refund detected. Keylight has already auto-revoked the license.
subscription.renewedonSubscription invoice paid (recurring billing only).

The two opt-in events have explicit toggles in Dashboard → Settings → Integrations → Webhook events.


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.


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"
}

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-…"
}

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
}

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.


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
}

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 event values).
  • Always route on event.event, not the URL or the order of keys.
  • Always dedupe on event.id — the same id may 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.