Source: https://www.revvly.ai/docs/guides/lifecycle-log

Guide

# Read the *lifecycle* event log.

The append-only record every metric derives from — how to query it, why it stores two deltas and two timestamps, and how to reconcile a dashboard number back to its events.

## One log, fourteen verbs

Every revenue-moving moment writes exactly one event: CREATED, RENEWED, REACTIVATED, EXPANDED, CONTRACTED, CANCEL_SCHEDULED, CHURNED, plus the migration and component events MIGRATE_MODULE, MIGRATE_BUNDLE, MIGRATE_COMPONENT, ADD_COMPONENT and REMOVE_COMPONENT. Subscription status is a convenience view; the event log is the source of truth. If a number in analytics looks wrong, the log is where you check — not the subscription document.

- [Lifecycle Event concept ›](https://www.revvly.ai/docs/concepts/lifecycleEvent)

## Two deltas per event

Each event stamps the MRR delta twice: in the subscription's native currency and converted to your reporting currency at that day's rate. FX shifts later never rewrite history — a EUR expansion from March keeps March's conversion forever, which is what makes month-over-month comparisons stable.

- [Exchange Rate concept ›](https://www.revvly.ai/docs/concepts/exchangeRate)

## Two timestamps per event

occurredAt is the economically real moment (the renewal boundary, the day the expansion took effect); createdAt is when the row was written. Backfills and late corrections make these differ. Analytics windows use a third field, attributedAt, which defaults to occurredAt. It differs only for usage movements, which are booked in the service period the usage was consumed in, not on the invoice date. A correction entered today for a March event still lands in March, where it belongs.

Which date an event is counted in

1. Event recordedoccurredAt is the economically real moment.
2. Any other eventattributedAt equals occurredAt.orUsage movementattributedAt falls in the service period the usage was consumed in.
3. Analytics windowFilters on attributedAt.

Only usage movements are counted in a different period than the one they occurred in.

## Query it

subscriptionLifecycleEvents(subscriptionId) returns one subscription's timeline, oldest first. organizationLifecycleEvents(organizationId, from?, to?) is the customer-wide view across all their subscriptions, capped at 1000 rows — use the date window to page through long histories.

## Corrections are new events

The log is append-only: nothing is ever updated or deleted. A wrong CHURNED gets balanced by a REACTIVATED; a mis-sized expansion gets a compensating CONTRACTED. This is deliberate — an audit trail you can edit is not an audit trail. If a delta looks odd, look for its correcting sibling before assuming a bug.

- [Lifecycle Event concept ›](https://www.revvly.ai/docs/concepts/lifecycleEvent)

## Reconcile analytics against it

MRR movement, ARR, NRR, and churn are all derived by summing event deltas over windows — mrrMovement's New/Expansion/Contraction/Churn buckets map one-to-one onto event types. To verify any dashboard figure, sum the reporting-currency deltas for that window yourself; monthlyDrillDown gives you the per-subscription rows behind any single bar.

