Event Architecture

Outbound Licenzy webhooks

Understand Licenzy's customer-facing outbound event stream and how it differs from inbound Stripe billing delivery.

Start free in test mode. Go live when you're ready.

Category: Event Architecture5 sections

Outbound events are Licenzy's event stream

Licenzy outbound webhooks are a separate mechanism from Stripe inbound webhooks. They are delivered asynchronously, signed with Licenzy HMAC headers, and should be treated as Licenzy's own event stream rather than raw forwarded Stripe payloads.

Outbound delivery is configured and inspected from the Licenzy portal, where you also review delivery history and retry operations.

Delivery contract lives separately from event families

This page explains what Licenzy emits. The delivery contract page explains how those events are delivered, retried, verified, and acknowledged operationally.

Read outbound webhook delivery contract before implementing a production receiver.

Current outbound event families

  • purchase.finalized
  • entitlement.updated
  • usage.consumed
  • subscription.updated
  • subscription.deleted
  • invoice.payment_succeeded
  • invoice.payment_failed

Source-aware and reason-aware payloads

Outbound payloads are source-aware and reason-aware. Nullable fields are expected when a field is not relevant to the current event.

  • source=stripe means the runtime change came from Stripe billing lifecycle processing and can include current Stripe context for the same event.
  • source=portal means the runtime change came from a manual operator workflow, includes actor_user_id, and does not backfill stale Stripe context.
  • source=runtime means the runtime change came from a runtime API operation such as usage consumption and includes runtime context only.

reason is operational context, not a closed enum. Treat it as a helpful explanation of why Licenzy emitted the event, not as the only field you use to infer state transitions.

  • Examples include checkout_finalized, manual_grant, manual_revoke, usage_consumed, refund, dispute, subscription_deleted, and invoice_payment_failed.
  • Nullable fields are expected when the current event does not need Stripe, portal, or runtime-specific context.

For the payload field glossary, see outbound payload glossary.

Realistic payload examples

purchase.finalizedConcrete integration example
JSON
{
  "subject_ref": "user_123",
  "project_id": "proj_123",
  "mode": "live",
  "product_code": "PRO_MONTHLY",
  "kind": "subscription",
  "status": "active",
  "reason": "checkout_finalized",
  "source": "stripe",
  "entitlement_id": "ent_123",
  "attempt_id": "att_123",
  "units": null,
  "usage_remaining": null,
  "amount_total": 7900,
  "currency": "usd",
  "stripe_checkout_session_id": "cs_live_123",
  "stripe_payment_intent_id": "pi_123",
  "stripe_subscription_id": "sub_123",
  "current_period_end": "2026-06-01T00:00:00.000Z",
  "expires_at": null,
  "canceled_at": null,
  "actor_user_id": null,
  "metadata": {},
  "created_at": "2026-05-18T09:00:00.000Z",
  "updated_at": "2026-05-18T09:00:00.000Z"
}
entitlement.updated from a manual portal actionConcrete integration example
JSON
{
  "subject_ref": "user_123",
  "project_id": "proj_123",
  "mode": "live",
  "product_code": "PACK_1000",
  "kind": "usage_pack",
  "status": "active",
  "reason": "manual_grant",
  "source": "portal",
  "entitlement_id": "ent_789",
  "attempt_id": null,
  "units": null,
  "usage_remaining": 1000,
  "amount_total": null,
  "currency": null,
  "stripe_checkout_session_id": null,
  "stripe_payment_intent_id": null,
  "stripe_subscription_id": null,
  "current_period_end": null,
  "expires_at": null,
  "canceled_at": null,
  "actor_user_id": "usr_123",
  "metadata": {
    "ticket_id": "sup_123"
  },
  "created_at": "2026-05-18T09:15:00.000Z",
  "updated_at": "2026-05-18T09:15:00.000Z"
}
usage.consumedConcrete integration example
JSON
{
  "subject_ref": "user_123",
  "project_id": "proj_123",
  "mode": "live",
  "product_code": "PACK_1000",
  "kind": "usage_pack",
  "status": "active",
  "reason": "usage_consumed",
  "source": "runtime",
  "entitlement_id": "ent_789",
  "attempt_id": null,
  "units": 10,
  "usage_remaining": 990,
  "amount_total": null,
  "currency": null,
  "stripe_checkout_session_id": null,
  "stripe_payment_intent_id": null,
  "stripe_subscription_id": null,
  "current_period_end": null,
  "expires_at": null,
  "canceled_at": null,
  "actor_user_id": null,
  "metadata": {
    "request_id": "req_987"
  },
  "created_at": "2026-05-18T09:20:00.000Z",
  "updated_at": "2026-05-18T09:20:00.000Z"
}
invoice.payment_failedConcrete integration example
JSON
{
  "subject_ref": "user_123",
  "project_id": "proj_123",
  "mode": "live",
  "product_code": "PRO_MONTHLY",
  "kind": "subscription",
  "status": "past_due",
  "reason": "invoice_payment_failed",
  "source": "stripe",
  "entitlement_id": "ent_123",
  "attempt_id": null,
  "units": null,
  "usage_remaining": null,
  "amount_total": 7900,
  "currency": "usd",
  "stripe_checkout_session_id": null,
  "stripe_payment_intent_id": null,
  "stripe_subscription_id": "sub_123",
  "current_period_end": "2026-06-01T00:00:00.000Z",
  "expires_at": null,
  "canceled_at": null,
  "actor_user_id": null,
  "metadata": {},
  "created_at": "2026-05-18T09:30:00.000Z",
  "updated_at": "2026-05-18T09:30:00.000Z"
}
subscription.deletedConcrete integration example
JSON
{
  "subject_ref": "user_123",
  "project_id": "proj_123",
  "mode": "live",
  "product_code": "PRO_MONTHLY",
  "kind": "subscription",
  "status": "canceled",
  "reason": "subscription_deleted",
  "source": "stripe",
  "entitlement_id": "ent_123",
  "attempt_id": null,
  "units": null,
  "usage_remaining": null,
  "amount_total": null,
  "currency": null,
  "stripe_checkout_session_id": null,
  "stripe_payment_intent_id": null,
  "stripe_subscription_id": "sub_123",
  "current_period_end": null,
  "expires_at": null,
  "canceled_at": "2026-05-18T10:00:00.000Z",
  "actor_user_id": null,
  "metadata": {},
  "created_at": "2026-05-18T10:00:00.000Z",
  "updated_at": "2026-05-18T10:00:00.000Z"
}