The Subscription Event Log provides event-level subscription data for your Prime Video channel. Access raw subscription events including Start, Renewal, Cancel, and autorenew status changes with anonymized customer identifiers to build custom analytics tailored to your business needs. This guide covers the data model, dimensions, metrics, and best practices for building subscription analytics.
Why Use the Subscription Event Log?
The Subscription Event Log provides a changelog of subscription analytics beyond what’s available in pre-built dashboards:
- Custom Analytics - Build metrics tailored to your specific business definitions and reporting needs using raw event-level data
- Data Warehouse Integration - Integrate Prime Video subscription data with your internal systems and other data sources for comprehensive analysis
- Flexible Business Logic - Apply your own settlement rules, deduplication logic, and metric calculations to match your business requirements
- Customer Lifecycle Tracking - Track individual customer journeys across subscription events with stable anonymized identifiers
How it works
The Subscription Event Log delivers subscription data as an append-only event stream via API. Each record represents a discrete event in a customer’s subscription lifecycle. The data contains raw events with no pre-applied business logic, giving you complete flexibility to calculate metrics according to your needs.
Choosing the Right Data Access Method
Slate Insights provides two ways to access subscription data:
Feature |
Slate Analytics Dashboard |
Subscription Event Log (API) |
Best for |
Quick insights, executive reporting |
Custom analytics, data engineering |
Data format |
Pre-aggregated metrics |
Raw event-level data |
Settlement logic |
Applied automatically |
You apply your own logic |
Deduplication |
Handled for you |
You deduplicate using last_update_time_utc |
Flexibility |
Fixed metric definitions |
Full customization |
Update frequency |
Approximately every 8 hours |
Approximately every 4 hours |
Historical data |
Up to 2 years |
Up to 2 years |
Access method |
Web UI |
REST API |
Use the Dashboard when you need quick answers or pre-built visualizations. Use the Event Log API when you need to build custom metrics, integrate with your data warehouse, or apply business-specific logic.
Key Concepts
Understanding these foundational concepts will help you work effectively with the Subscription Event Log.
Concept |
Description |
Event |
A discrete subscription action (Start, Renewal, Cancel, Auto Renewal On/Off). Each event is identified by a unique subscription_event_id. |
Dimension |
An attribute that describes the context of an event, such as the date it occurred, the customer involved, or the offer type. Dimensions are used to filter and group data. |
Metric |
A calculated measurement derived from events, such as active subscriber count, churn rate, or conversion rate. Metrics are not stored directly—you calculate them from the event data. |
Changelog model |
Data is append-only. If an event’s attributes change, a new record is published with the same subscription_event_id but a newer last_update_time_utc. |
Settlement |
Business logic that filters noise from raw events. For example, excluding same-day start/cancel pairs that don’t represent meaningful subscriptions. |
Lifecycle |
A subscription “cycle” representing a continuous period of subscription. A new lifecycle begins after each settled Cancel event. |
Note: Start with Events and Dimensions to query data, apply Settlement and Lifecycle logic to clean it, then calculate Metrics matching your business definitions.
Dimensions
Dimensions are attributes that describe the context of each subscription event. They are used to filter data, group results, and build reports.
Event Dimension
Subscription_event_type is the type of subscription event that occurred. This is the primary dimension for understanding customer lifecycle actions.
Value |
Description |
Use Case |
Start |
Customer subscribed to your channel for the first time. At the time of vend, no prior Start record exists for this customer. |
Count new subscriber acquisitions |
Renewal |
Customer’s subscription continued from any previous period in the past. Renewal events occur even when gaps exist between subscription periods, provided the customer has a historical subscription record. |
Count retained subscribers |
Cancel |
Customer’s subscription ended. May be customer-initiated or due to payment failure. |
Calculate churn |
Active - AR ON |
Customer enabled automatic renewal. Subscription will renew at period end. |
Track autorenew rate |
Active - AR OFF |
Customer disabled automatic renewal. Subscription will expire at period end unless re-enabled. |
Identify at-risk subscribers |
Suspended |
Customer’s subscription is temporarily paused due to payment issues (insufficient funds or invalid billing information). The subscription will automatically resume and generate a Renewal event once the customer updates their billing information and payment is successfully processed. |
Helps to track subscriptions in a temporary failure state that could either recover (becoming Renewals) or convert to Cancellations |
Note: A Start event only occurs when, at the time of vend, we have no prior Start record for that customer on your channel. For reliable active subscriber counting, treat both Start and Renewal events as indicators of an active subscription. This approach handles all edge cases consistently.
Offer Dimensions
These dimensions describe the subscription offer associated with each event.
Dimension |
Type |
Format |
Description |
is_promo |
integer |
0 or 1 |
Indicates whether the subscription event occurred during a promotional offer period. 0 = Standard offer (no promotion active), 1 = Promotional offer (discount or special pricing active). |
offer_type |
string |
Text |
Represents the offer classification to Amazon. This will always be ‘3P_SUBS’ for Channels. |
offer_marketplace |
string |
Marketplace code |
The Amazon marketplace where the subscription offer is available. Values: US (United States), UK (United Kingdom), DE (Germany), JP (Japan), ROE (Rest of Europe), ROW_NA (Rest of World — North America), ROW_EU (Rest of World — Europe), ROW_FE (Rest of World — Far East). The offer_marketplace reflects where the offer was purchased, which may differ from the customer’s current location. |
offer_name |
string |
Text |
The human-readable name of the subscription offer as configured in your channel setup. |
offer_payment_amount |
numeric |
Numeric value (e.g., “4.99", “9.99") |
The list price of the subscription offer in the local currency. This field is offer metadata only and does not represent transactional revenue. This field shows the price of the offer associated with the event—it does not represent actual revenue earned or money received. The same value appears on every event type (Start, Renewal, Cancel, AR changes) regardless of whether payment occurred. You cannot sum this field to calculate revenue. For actual revenue data, refer to your separate financial reporting systems. |
offer_billing_type |
string |
FT or HO |
The payment model for the subscription offer. FT = Free Trial (introductory period at no charge), HO = Hard Offer (paid subscription). |
offer_id |
string |
Identifier |
The unique identifier for the subscription offer. Use this to join with offer metadata or track performance by offer. |
Note: The offer_marketplace indicates where the subscription was purchased and determines pricing, currency, and content availability. A single customer may have subscriptions across multiple marketplaces. Use offer_billing_type to distinguish between free trial and paid subscription events when calculating conversion metrics.
Customer Dimensions
These dimensions identify and describe the subscriber.
Dimension |
Type |
Format |
Description |
cid |
string |
Anonymized identifier |
An anonymized, stable customer identifier. This ID is consistent across all events for a given customer within your channel, enabling you to track complete customer lifecycles. The cid is consistent within your parent channel. The same customer will have the same cid across all offers and bundles that roll up to the same parent channel within a marketplace (For example, a customer who subscribes to “Prime Ad-Supported (US)” and later switches to “Prime Ad-free (US)” would retain the same cid because both offers share the parent channel “Prime (US)”). Customers will have different cid values across different partner channels, ensuring privacy while enabling per-channel analytics. |
benefit_id |
string |
Identifier |
The Prime Video benefit ID associated with the subscription. This links the subscription to specific content access rights. |
channel_label |
string |
Text |
The name of the channel the subscription belongs to. |
channel_tier_label |
string |
Text |
The name of the specific tier within the channel, if applicable. If your channel does not use tiers, this value may be null or match the channel_label. |
Note: The cid enables customer lifecycle tracking while maintaining privacy. Use cid to analyze individual customer behavior patterns, calculate retention metrics, and identify re-subscription events.
Time Dimensions
These dimensions provide temporal context for subscription events.
Dimension |
Type |
Format |
Description |
subscription_event_time_utc |
timestamp |
ISO 8601: YYYY-MM-DDTHH:MM:SS.sssZ |
The timestamp when the subscription event occurred, stored in UTC. All event timestamps are stored in UTC for consistency. Use subscription_event_time_zone to convert to local time for reporting. |
subscription_event_time_zone |
string |
Time zone identifier (e.g., America/New_York, Europe/London) |
The time zone of the marketplace where the subscription event occurred. Slate Analytics dashboard presents data in local timezone. |
create_time_utc |
timestamp |
ISO 8601 format |
The timestamp when the subscription event record was first created in the changelog, stored in UTC. This represents when the record was added to the changelog, not when the event occurred. Use subscription_event_time_utc for the actual event time. |
last_update_time_utc |
timestamp |
ISO 8601 format |
The timestamp when the subscription event record was last updated in the changelog, stored in UTC. Always use this field to deduplicate records. When multiple records exist for the same subscription_event_id, keep only the record with the latest last_update_time_utc. |
Example conversion (Athena SQL):
-- Parse ISO 8601 string and convert to local timezone
CAST(DATE_FORMAT(
AT_TIMEZONE(
parse_datetime(subscription_event_time_utc, 'yyyy-MM-dd''T''HH:mm:ss.SSS''Z'''),
subscription_event_time_zone
),
'%Y-%m-%d %H:%i:%s.%f'
) AS TIMESTAMP) AS event_time_local
Note: Use subscription_event_time_utc for all calculations and filtering. Convert to local timezone using subscription_event_time_zone only for reporting and display purposes.
Record Management Dimensions
These dimensions help you manage the changelog data correctly.
Dimension |
Type |
Format |
Description |
subscription_event_id |
string |
UUID format |
The unique identifier for each subscription event. This is the primary key for deduplication. Multiple records may exist for the same subscription_event_id if the event’s attributes were updated. Always deduplicate by keeping the record with the latest last_update_time_utc. |
is_deleted |
integer |
0 or 1 |
Indicates whether a previously published record should be removed from your dataset. 0 = Active record (include in analysis), 1 = Deleted record (exclude from analysis - remove from your tables if previously ingested). Deleted records are rare but may occur when data corrections are necessary. Always filter is_deleted = 0 in your queries. |
Note: Always deduplicate using last_update_time_utc before performing any analysis. Filter out deleted records (is_deleted = 1) at the start of your ETL pipeline to ensure data accuracy.
Data Cleansing & Settlement Logic
This section explains the two-stage deduplication process used to transform raw event data into clean, business-meaningful metrics. Some partners use different settlement logic, so we provide raw event data to enable flexibility. This section provides the logic we use at Prime Video so that partners can reconcile metrics with the Slate Analytics Dashboard and see how we model the data.
Why does Settlement Matter?
Raw subscription event data contains noise that can inflate or distort your metrics:
Issue |
Example |
Impact Without Settlement |
Same-day churn |
Customer starts and cancels within hours |
Double-counted as both a start and a cancel |
Payment retries |
Multiple Cancel events from billing failures |
Inflated churn counts |
Cancels without starts |
Cancel events without a corresponding Start in the lifecycle |
Inflated churn without matching acquisition |
The settlement logic ensures that your metrics reflect meaningful subscription activity, not system-generated noise.
Stage 1: End of Day Settlement
Handle same-day Start + Cancel pairs by keeping only the End-of-Day (EOD) state. If a customer has both a Start/Renewal and a Cancel event on the same calendar day, keep only the chronologically last event for that day. Same-day cancellations often indicate payment authorization failures or signup flow issues. These don’t represent meaningful subscriber engagement and counting both inflates starts and cancels
Implementation
-- Step 1: Identify same-day start + cancel pairs
same_day_start_and_cancel AS (
SELECT cid, offer_marketplace, event_date_local
FROM deduped_events
GROUP BY cid, offer_marketplace, event_date_local
HAVING SUM(CASE WHEN subscription_event_type IN ('Start', 'Renewal') THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN subscription_event_type = 'Cancel' THEN 1 ELSE 0 END) > 0
),
-- Step 2: Rank events by time within each day
events_with_eod_rank AS (
SELECT e.*,
ROW_NUMBER() OVER (
PARTITION BY e.cid, e.offer_marketplace, e.event_date_local
ORDER BY e.event_time_local DESC
) AS eod_rank
FROM deduped_events e
),
-- Step 3: For same-day pairs, keep only EOD event (rank = 1)
settled_events AS (
SELECT e.*
FROM events_with_eod_rank e
LEFT JOIN same_day_start_and_cancel s
ON e.cid = s.cid
AND e.offer_marketplace = s.offer_marketplace
AND e.event_date_local = s.event_date_local
WHERE s.cid IS NULL -- Not a same-day pair, keep all events
OR e.eod_rank = 1 -- Same-day pair, keep only last event
)
Example
This table demonstrates how EOD settlement logic handles same-day Start and Cancel event pairs on 2025/10/15 by keeping only the final state at end of day.
Time |
Event |
Without EOD Settlement |
With EOD Settlement |
2025/08/01 00:00 |
Start |
Counted as Start |
Counted as Start |
2025/09/01 00:00 |
Renewal |
- |
- |
2025/10/01 00:00 |
Renewal |
- |
- |
2025/10/15 07:00 |
Cancel |
Counted as Cancel |
Excluded |
2025/10/15 09:00 |
Start |
Counted as Start |
Excluded |
2025/10/15 09:15 |
Cancel |
Counted as Cancel |
Counted as Cancel |
Note: The customer is counted only as a Cancel (or not at all, depending on lifecycle context), not as both a Start and a Cancel. EOD settlement applies only to settled metrics (starts, cancels). Snapshot metrics use subscription spans built from changelog-deduped events without EOD settlement to preserve the full event timeline needed for point-in-time calculations and conversions.
Stage 2: Lifecycle Deduplication
Group events into subscription “cycles” and keep only the first Start and first Cancel per lifecycle. A valid Cancel requires a corresponding Start in the same lifecycle.
Implementation-- Step 1: Identify lifecycle boundaries
-- A new lifecycle starts when there's no previous event OR previous event was Cancel
events_with_lifecycle_boundaries AS (
SELECT e.*,
LAG(subscription_event_type) OVER (
PARTITION BY cid, offer_marketplace
ORDER BY event_time_local
) AS prev_event_type
FROM settled_events e
),
-- Step 2: Flag lifecycle starts
events_with_lifecycle_start_flag AS (
SELECT e.*,
CASE
WHEN prev_event_type IS NULL THEN 1 -- First event ever
WHEN prev_event_type = 'Cancel' THEN 1 -- After a cancel
ELSE 0
END AS is_lifecycle_start
FROM events_with_lifecycle_boundaries e
),
-- Step 3: Assign lifecycle numbers using cumulative sum
events_with_lifecycle_id AS (
SELECT e.*,
SUM(is_lifecycle_start) OVER (
PARTITION BY cid, offer_marketplace
ORDER BY event_time_local
ROWS UNBOUNDED PRECEDING
) AS lifecycle_num
FROM events_with_lifecycle_start_flag e
),
-- Step 4: Keep only first Start and first Cancel per lifecycle
events_with_dedup_rank AS (
SELECT e.*,
ROW_NUMBER() OVER (
PARTITION BY cid, offer_marketplace, lifecycle_num,
CASE WHEN subscription_event_type IN ('Start', 'Renewal') THEN 'Start' END
ORDER BY event_time_local
) AS start_rank,
ROW_NUMBER() OVER (
PARTITION BY cid, offer_marketplace, lifecycle_num,
CASE WHEN subscription_event_type = 'Cancel' THEN 'Cancel' END
ORDER BY event_time_local
) AS cancel_rank
FROM events_with_lifecycle_id e
),
deduped_lifecycle_events AS (
SELECT *
FROM events_with_dedup_rank
WHERE (subscription_event_type IN ('Start', 'Renewal') AND start_rank = 1)
OR (subscription_event_type = 'Cancel' AND cancel_rank = 1 AND lifecycle_has_start = 1)
)
The lifecycle_has_start = 1 condition ensures cancels are only counted when the lifecycle has a corresponding start event.
Example
Customer |
Event |
Time |
Lifecyle |
Kept? |
ABC |
Start |
1/1/2025 |
1 |
✓ First Start in lifecycle 1 |
ABC |
Renewal |
2/1/2025 |
1 |
✗ Not first Start in lifecycle 1 |
ABC |
Cancel |
3/1/2025 |
1 |
✓ First Cancel in lifecycle 1 |
ABC |
Start |
4/15/2025 |
2 |
✓ First Start in lifecycle 2 |
ABC |
Cancel |
5/15/2025 |
2 |
✓ First Cancel in lifecycle 2 |
Important: Like EOD settlement, lifecycle deduplication is applied only to settled metrics. Snapshot metrics use the raw subscription spans where each Start/Renewal creates a span that ends at the next event, preserving the complete timeline needed for point-in-time calculations.
Metrics
Metrics are calculated measurements derived from subscription events. Unlike dimensions, metrics are not stored directly in the Event Log but you calculate them by aggregating and transforming the event data. Note, the calculation formulas provided are recommendations; you may adjust them to match your business definitions.
Core Metrics
The following metrics are fundamental to subscription analytics:
Metric |
Description |
End of Period Subscribers |
Customers with a current subscription at the end of the period |
Active Subscribers |
Customers that has an active subscription anytime during the period |
Subscriber Acquisition (Starts) |
New subscribers gained |
Cancellations |
Subscribers who canceled |
Free Trial Conversion Rate |
Free trial to paid conversion |
Net Paid Starts |
New paying subscribers accounting for conversions |
Subscriber Metrics
These metrics track new subscriber acquisitions and cancellations after applying settlement and lifecycle deduplication.
Metric |
Definition |
Calculation |
num_starts |
Count of Start and Renewal events marking the beginning of a new subscription lifecycle within the reporting period. |
After applying settlement and lifecycle deduplication, count events where subscription_event_type IN (‘Start’, ‘Renewal’) and event falls within the period. SUM(CASE WHEN subscription_event_type IN (‘Start', ‘Renewal') AND event_time_local >= period_start AND event_time_local < period_end THEN 1 ELSE 0 END) AS num_starts |
num_starts_ft |
Count of starts where the offer billing type is Free Trial (FT). |
SUM(CASE WHEN subscription_event_type IN (‘Start', ‘Renewal') AND offer_billing_type = ‘FT’ AND event_time_local >= period_start AND event_time_local < period_end THEN 1 ELSE 0 END) AS num_starts_ft |
num_starts_ho |
Count of starts where the offer billing type is Hard Offer (HO) — paid subscriptions. |
SUM(CASE WHEN subscription_event_type IN (‘Start', ‘Renewal') AND offer_billing_type = ‘HO’ AND event_time_local >= period_start AND event_time_local < period_end THEN 1 ELSE 0 END) AS num_starts_ho |
num_net_starts_ho |
Net new paying subscribers, calculated as direct HO starts plus free trial conversions. Provides a complete picture of new paying subscribers whether they signed up directly to a paid plan or converted from a free trial. |
num_net_starts_ho = num_starts_ho + num_ft_conversions |
num_cancels |
Count of Cancel events within the reporting period after applying settlement and lifecycle deduplication. |
SUM(CASE WHEN subscription_event_type = ‘Cancel’ AND event_time_local >= period_start AND event_time_local < period_end THEN 1 ELSE 0 END) AS num_cancels |
num_cancels_ho |
Cancellations where the subscriber was on a Hard Offer (paid) plan. |
- |
num_cancels_ft |
Cancellations where the subscriber was on a Free Trial plan (trial ended without conversion). |
- |
Period Metrics
These metrics provide point-in-time snapshots and period activity using subscription spans built without EOD settlement.
Metric |
Definition |
Calculation |
Variants |
num_end_period_subs |
Count of unique subscribers who were active at the end of the reporting period. This is a point-in-time snapshot. |
From subscription_spans (built without EOD settlement) COUNT(DISTINCT CASE WHEN span_start_date < period_end AND (span_end_date IS NULL OR span_end_date >= period_end) THEN cid END) AS num_end_period_subs |
num_end_period_subs_ho — End-of-period subscribers on Hard Offer plans, num_end_period_subs_ft — End-of-period subscribers on Free Trial plans |
num_active_period_subs |
Count of unique subscribers who were active at any point during the reporting period. Captures all customers who had an active subscription span overlapping with the period, regardless of when they started or canceled. |
From subscription_spans (built without EOD settlement) COUNT(DISTINCT CASE WHEN span_start_date < period_end AND (span_end_date IS NULL OR span_end_date >= period_start) THEN cid END) AS num_active_period_subs |
num_active_period_subs_ho — Subscribers active at any point during the period on Hard Offer (paid) plans - num_active_period_subs_ft — Subscribers active at any point during the period on Free Trial plans |
Note: Period metrics use subscription_spans built without EOD settlement to preserve the full event timeline.
Free trial metrics
These metrics track free trial conversions and settlement using subscription spans built without EOD settlement.
Metric |
Definition |
Calculation |
num_ft_conversions |
Count of free trial subscribers who converted to a paid (Hard Offer) plan within the reporting period. A conversion occurs when a subscriber on a Free Trial (offer_billing_type = ‘FT’) has their next event be a Start/Renewal with offer_billing_type = ‘HO’. |
From subscription_spans (built without EOD settlement) COUNT(CASE WHEN offer_billing_type = ‘FT’ AND span_end_billing_type = ‘HO’ AND span_end_event_type IN (‘Start', ‘Renewal') AND span_end_date >= period_start AND span_end_date < period_end THEN 1 END) AS num_ft_conversions |
num_ft_settled |
Count of free trial subscriptions that settled (reached a conclusion) within the reporting period. A trial settles when the subscriber either converts to paid or cancels (next billing type is not FT). |
COUNT(CASE WHEN offer_billing_type = ‘FT’ AND span_end_date IS NOT NULL AND span_end_date >= period_start AND span_end_date < period_end THEN 1 END) AS num_ft_settled |
ft_conversion_rate_pct |
Percentage of settled free trials that converted to paid subscriptions. |
CASE WHEN num_ft_settled > 0 THEN ROUND(100.0 * num_ft_conversions / num_ft_settled, 2) ELSE 0 END AS ft_conversion_rate_pct |
Note: Free trial metrics use subscription_spans built without EOD settlement to accurately track conversions where customers switch from one offer_id to another.
Understanding Event Patterns
This section explains common event patterns you’ll encounter in the Subscription Event Log and how to interpret them correctly.
Subscription Lifecycle
A typical subscription follows this lifecycle, progressing through four key stages from initial sign-up through renewal cycles and potential cancellation. Each stage generates specific event types that enable you to track customer behavior and calculate subscription metrics.
Key Lifecyle Points
These key lifecycle points represent the four critical stages in a customer’s subscription journey, each generating distinct event types that enable tracking of acquisition, retention, engagement, and churn patterns.
Stage |
Event Type |
Description |
Acquisition |
Start |
New customer joins your channel |
Retention |
Renewal |
Existing customer continues subscription |
Engagement |
Active - AR ON/OFF |
Customer adjusts autorenew preference |
Churn |
Cancel |
Customer’s subscription ends |
Common Event Patterns
Pattern 1: Renewal Without a Start
A customer’s event history shows Renewal events but no Start. The customer’s original Start event occurred more than 2 years ago. Due to the data retention policy, events older than 2 years are not available. The Renewal events within the window are still accessible.
Pattern 2: Multiple Cancel Events
A customer has several Cancel events within minutes or hours. This pattern typically indicates payment retry failures: The billing system attempts to charge the customer’s payment method. Each failed attempt may generate a Cancel event. The customer may be attempting to resubscribe with a declined card. The lifecycle deduplication will keep only the first Cancel per lifecycle.
Pattern 3: Same-Day Start and Cancel
A customer has both a Start and Cancel on the same calendar day. Common causes include:
- Customer signed up and immediately canceled
- Payment authorization failed after initial signup
- Fraudulent signup attempt blocked
EOD Settlement handles this automatically, only the final state is kept.
Pattern 4: Free Trial to Paid Conversion
A customer has a Start with offer_billing_type = ‘FT’, followed by another Start or Renewal with offer_billing_type = ‘HO’. The customer completed their free trial and converted to a paid subscription. Use LEAD() window functions to detect when offer_billing_type changes from FT to HO - Count as a conversion when the transition event falls within your reporting period.
Auto renew events
Auto Renew (AR) events track when customers enable or disable automatic subscription renewal. These events provide early signals for predicting future churn and identifying at-risk subscribers.
Event Type |
Description |
Use Case |
AR_ON |
Customer has enabled automatic renewal. Their subscription will automatically renew at the end of the current period. |
Track customers who have committed to continuing their subscription. |
AR_OFF |
Customer has disabled automatic renewal. Their subscription will expire at the end of the current period unless they re-enable auto-renewal. |
Early Warning Signal: Customers who disable auto-renewal are signaling intent to leave before their subscription actually ends. Churn Forecasting: AR OFF subscribers will definitively churn at period end unless they re-enable auto-renewal. This makes AR OFF events one of the most reliable predictors of future churn. |
Note: Autorenew events are logged whenever a customer actively toggles their auto-renewal preference through the subscription management interface.
Analyzing AR events
To analyze auto-renewal behavior, query AR ON/OFF events separately from the main lifecycle metrics pipeline. AR events follow the same changelog deduplication rules but are not included in the Start/Renewal/Cancel lifecycle calculations.
-- Example:Filter to AR Off Events
SELECT *
FROM deduped_events
WHERE subscription_event_type = 'AR_OFF'
GROUP BY 1, 2
ORDER BY 1, 2
ETL Pipeline
Building upon the above metric definitions and data transformations, use this ETL (Extract, Transform, Load) pipeline to transform raw Subscription Event Log data into clean, business-meaningful metrics. This query can be run directly in Amazon Athena.
The pipeline implements a two-path architecture to correctly calculate different metric types:
- Path A: Settled Events — Applies EOD settlement AND lifecycle deduplication. Used for settled metrics (starts, cancels). This filters out noise like same-day start+cancel pairs that don’t represent true acquisitions.
- Path B: Subscription Spans — Applies changelog deduplication ONLY. Used for snapshot metrics (end-of-period counts, FT conversions).
-- ============================================================================
-- SUBSCRIPTION METRICS ETL PIPELINE
-- ============================================================================
-- Transforms raw subscription events into clean metrics using TWO PATHS:
-- PATH A: Settled Events - for settled metrics (starts, cancels)
-- Applies: Changelog dedup → EOD settlement → Lifecycle dedup
-- Filters out noise like same-day start+cancel pairs
-- PATH B: Subscription Spans - for snapshot metrics (end-of-period, conversions)
-- Applies: Changelog dedup ONLY (preserves full timeline)
-- Required for conversions because customer switches offer_id (A → B)
-- ============================================================================
WITH
-- =============================================================================
-- STEP 1: Parse timestamps and convert to local timezone
-- =============================================================================
parsed_events AS (
SELECT
subscription_event_id,
subscription_event_type,
subscription_event_time_utc,
subscription_event_time_zone,
cid,
offer_id,
offer_name,
offer_marketplace,
offer_billing_type,
offer_payment_amount,
is_promo,
channel_label,
channel_tier_label,
benefit_id,
is_deleted,
last_update_time_utc,
-- Parse and convert timestamps
parse_datetime(subscription_event_time_utc, 'yyyy-MM-dd''T''HH:mm:ss.SSS''Z''') AS event_time_utc,
CAST(DATE_FORMAT(
AT_TIMEZONE(
parse_datetime(subscription_event_time_utc, 'yyyy-MM-dd''T''HH:mm:ss.SSS''Z'''),
subscription_event_time_zone
),
'%Y-%m-%d %H:%i:%s.%f'
) AS TIMESTAMP) AS event_time_local,
CAST(DATE_FORMAT(
AT_TIMEZONE(
parse_datetime(subscription_event_time_utc, 'yyyy-MM-dd''T''HH:mm:ss.SSS''Z'''),
subscription_event_time_zone
),
'%Y-%m-%d'
) AS DATE) AS event_date_local
FROM your_subscription_events_table
WHERE is_deleted = 0
AND subscription_event_type IN ('Start', 'Renewal', 'Cancel')
),
-- =============================================================================
-- STEP 2: Deduplicate changelog records (used by BOTH paths)
-- =============================================================================
deduped_events AS (
SELECT * FROM (
SELECT
*,
ROW_NUMBER() OVER (
PARTITION BY subscription_event_id
ORDER BY last_update_time_utc DESC
) AS dedup_rank
FROM parsed_events
)
WHERE dedup_rank = 1
),
-- =============================================================================
-- PATH A: SETTLED EVENTS (for starts, cancels)
-- Applies: Changelog dedup → EOD settlement → Lifecycle dedup
-- =============================================================================
-- STEP A1: EOD Settlement - Identify same-day Start+Cancel pairs
same_day_start_and_cancel AS (
SELECT cid, offer_marketplace, event_date_local
FROM deduped_events
GROUP BY cid, offer_marketplace, event_date_local
HAVING
SUM(CASE WHEN subscription_event_type IN ('Start', 'Renewal') THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN subscription_event_type = 'Cancel' THEN 1 ELSE 0 END) > 0
),
-- Rank events by time within each day
events_with_eod_rank AS (
SELECT
e.*,
ROW_NUMBER() OVER (
PARTITION BY e.cid, e.offer_marketplace, e.event_date_local
ORDER BY e.event_time_local DESC
) AS eod_rank
FROM deduped_events e
),
-- Apply EOD settlement: for same-day pairs, keep only the last event
eod_settled_events AS (
SELECT e.*
FROM events_with_eod_rank e
LEFT JOIN same_day_start_and_cancel sd
ON e.cid = sd.cid
AND e.offer_marketplace = sd.offer_marketplace
AND e.event_date_local = sd.event_date_local
WHERE sd.cid IS NULL OR e.eod_rank = 1
),
-- STEP A2: Build customer lifecycles (on EOD settled data)
settled_events_with_prev AS (
SELECT
*,
LAG(subscription_event_type) OVER (
PARTITION BY cid, offer_marketplace
ORDER BY event_time_local
) AS prev_event_type
FROM eod_settled_events
),
settled_events_with_lifecycle_flag AS (
SELECT
*,
CASE
WHEN prev_event_type IS NULL THEN 1
WHEN prev_event_type = 'Cancel' THEN 1
ELSE 0
END AS is_lifecycle_start
FROM settled_events_with_prev
),
settled_events_with_lifecycle AS (
SELECT
*,
SUM(is_lifecycle_start) OVER (
PARTITION BY cid, offer_marketplace
ORDER BY event_time_local
ROWS UNBOUNDED PRECEDING
) AS lifecycle_num,
cid || '|' || offer_marketplace || '|' || CAST(
SUM(is_lifecycle_start) OVER (
PARTITION BY cid, offer_marketplace
ORDER BY event_time_local
ROWS UNBOUNDED PRECEDING
) AS VARCHAR
) AS lifecycle_id
FROM settled_events_with_lifecycle_flag
),
-- STEP A3: Lifecycle deduplication
events_with_type_rank AS (
SELECT
*,
CASE WHEN subscription_event_type IN ('Start', 'Renewal') THEN
ROW_NUMBER() OVER (
PARTITION BY lifecycle_id,
CASE WHEN subscription_event_type IN ('Start', 'Renewal') THEN 'START' END
ORDER BY event_time_local
)
END AS start_rank,
CASE WHEN subscription_event_type = 'Cancel' THEN
ROW_NUMBER() OVER (
PARTITION BY lifecycle_id,
CASE WHEN subscription_event_type = 'Cancel' THEN 'CANCEL' END
ORDER BY event_time_local
)
END AS cancel_rank,
MAX(CASE WHEN subscription_event_type IN ('Start', 'Renewal') THEN 1 ELSE 0 END) OVER (
PARTITION BY lifecycle_id
) AS lifecycle_has_start
FROM settled_events_with_lifecycle
),
settled_events AS (
SELECT *
FROM events_with_type_rank
WHERE
(subscription_event_type IN ('Start', 'Renewal') AND start_rank = 1)
OR (subscription_event_type = 'Cancel' AND cancel_rank = 1 AND lifecycle_has_start = 1)
),
-- =============================================================================
-- PATH B: SUBSCRIPTION SPANS (for conversions, end-of-period)
-- Applies: Changelog dedup ONLY (NO EOD settlement, NO lifecycle dedup)
-- This preserves the full timeline needed for accurate span calculations
-- =============================================================================
-- STEP B1: Calculate LEAD() on ALL deduped events (before any filtering)
events_with_span_boundaries AS (
SELECT
cid,
offer_marketplace,
offer_id,
offer_billing_type,
subscription_event_type,
event_time_local,
event_date_local,
LEAD(event_time_local) OVER (
PARTITION BY cid, offer_marketplace
ORDER BY event_time_local
) AS next_event_time,
LEAD(subscription_event_type) OVER (
PARTITION BY cid, offer_marketplace
ORDER BY event_time_local
) AS next_event_type,
LEAD(offer_billing_type) OVER (
PARTITION BY cid, offer_marketplace
ORDER BY event_time_local
) AS next_billing_type
FROM deduped_events -- Uses deduped_events directly, NOT eod_settled_events
),
-- STEP B2: Create spans from Start/Renewal events
subscription_spans AS (
SELECT
cid,
offer_marketplace,
offer_id,
offer_billing_type,
subscription_event_type,
event_time_local AS span_start,
event_date_local AS span_start_date,
next_event_time AS span_end,
CAST(next_event_time AS DATE) AS span_end_date,
next_event_type AS span_end_event_type,
next_billing_type AS span_end_billing_type
FROM events_with_span_boundaries
WHERE subscription_event_type IN ('Start', 'Renewal')
),
-- =============================================================================
-- STEP 3: Generate reporting periods
-- =============================================================================
reporting_periods AS (
SELECT
period_month,
CAST(period_month AS DATE) AS period_start,
CAST(DATE_ADD('month', 1, period_month) AS DATE) AS period_end
FROM UNNEST(SEQUENCE(DATE '2025-01-01', DATE '2025-12-01', INTERVAL '1' MONTH)) AS t(period_month)
),
-- =============================================================================
-- STEP 4A: SETTLED METRICS (from settled events - with EOD + lifecycle dedup)
-- Use for: starts, cancels
-- =============================================================================
settled_metrics AS (
SELECT
DATE_FORMAT(p.period_start, '%Y-%m') AS report_month,
e.offer_marketplace,
-- Starts (all)
SUM(CASE WHEN e.subscription_event_type IN ('Start', 'Renewal')
AND e.event_date_local >= p.period_start
AND e.event_date_local < p.period_end
THEN 1 ELSE 0 END) AS num_starts,
-- Starts (Free Trial)
SUM(CASE WHEN e.subscription_event_type IN ('Start', 'Renewal')
AND e.offer_billing_type = 'FT'
AND e.event_date_local >= p.period_start
AND e.event_date_local < p.period_end
THEN 1 ELSE 0 END) AS num_starts_ft,
-- Starts (Hard Offer / Paid)
SUM(CASE WHEN e.subscription_event_type IN ('Start', 'Renewal')
AND e.offer_billing_type = 'HO'
AND e.event_date_local >= p.period_start
AND e.event_date_local < p.period_end
THEN 1 ELSE 0 END) AS num_starts_ho,
-- Cancels (all)
SUM(CASE WHEN e.subscription_event_type = 'Cancel'
AND e.event_date_local >= p.period_start
AND e.event_date_local < p.period_end
THEN 1 ELSE 0 END) AS num_cancels,
-- Cancels (Hard Offer)
SUM(CASE WHEN e.subscription_event_type = 'Cancel'
AND e.offer_billing_type = 'HO'
AND e.event_date_local >= p.period_start
AND e.event_date_local < p.period_end
THEN 1 ELSE 0 END) AS num_cancels_ho,
-- Cancels (Free Trial)
SUM(CASE WHEN e.subscription_event_type = 'Cancel'
AND e.offer_billing_type = 'FT'
AND e.event_date_local >= p.period_start
AND e.event_date_local < p.period_end
THEN 1 ELSE 0 END) AS num_cancels_ft
FROM settled_events e
CROSS JOIN reporting_periods p
GROUP BY DATE_FORMAT(p.period_start, '%Y-%m'), e.offer_marketplace
),
-- =============================================================================
-- STEP 4B: SNAPSHOT METRICS (from subscription spans - NO EOD settlement)
-- Use for: end-of-period counts, conversions, settled trials
-- =============================================================================
snapshot_metrics AS (
SELECT
DATE_FORMAT(p.period_start, '%Y-%m') AS report_month,
s.offer_marketplace,
-- End of period counts (who was active at period end?)
COUNT(DISTINCT CASE
WHEN s.span_start_date < p.period_end
AND (s.span_end_date IS NULL OR s.span_end_date >= p.period_end)
THEN s.cid
END) AS num_end_period_subs,
COUNT(DISTINCT CASE
WHEN s.offer_billing_type = 'HO'
AND s.span_start_date < p.period_end
AND (s.span_end_date IS NULL OR s.span_end_date >= p.period_end)
THEN s.cid
END) AS num_end_period_subs_ho,
COUNT(DISTINCT CASE
WHEN s.offer_billing_type = 'FT'
AND s.span_start_date < p.period_end
AND (s.span_end_date IS NULL OR s.span_end_date >= p.period_end)
THEN s.cid
END) AS num_end_period_subs_ft,
-- Active at any point during the period
COUNT(DISTINCT CASE
WHEN s.span_start_date < p.period_end
AND (s.span_end_date IS NULL OR s.span_end_date >= p.period_start)
THEN s.cid
END) AS num_active_period_subs,
COUNT(DISTINCT CASE
WHEN s.offer_billing_type = 'HO'
AND s.span_start_date < p.period_end
AND (s.span_end_date IS NULL OR s.span_end_date >= p.period_start)
THEN s.cid
END) AS num_active_period_subs_ho,
COUNT(DISTINCT CASE
WHEN s.offer_billing_type = 'FT'
AND s.span_start_date < p.period_end
AND (s.span_end_date IS NULL OR s.span_end_date >= p.period_start)
THEN s.cid
END) AS num_active_period_subs_ft,
-- Conversions (FT span ending with HO start in this period)
COUNT(CASE
WHEN s.offer_billing_type = 'FT'
AND s.span_end_billing_type = 'HO'
AND s.span_end_event_type IN ('Start', 'Renewal')
AND s.span_end_date >= p.period_start
AND s.span_end_date < p.period_end
THEN 1
END) AS num_ft_conversions,
-- FT Settled (FT span that ended in this period)
COUNT(CASE
WHEN s.offer_billing_type = 'FT'
AND s.span_end_date IS NOT NULL
AND s.span_end_date >= p.period_start
AND s.span_end_date < p.period_end
THEN 1
END) AS num_ft_settled
FROM subscription_spans s
CROSS JOIN reporting_periods p
GROUP BY DATE_FORMAT(p.period_start, '%Y-%m'), s.offer_marketplace
)
-- =============================================================================
-- FINAL OUTPUT: Combine flow and snapshot metrics
-- =============================================================================
SELECT
sm.report_month,
sm.offer_marketplace,
-- Starts (from settled events)
sm.num_starts,
sm.num_starts_ft,
sm.num_starts_ho,
-- Net Starts (paid starts + conversions)
sm.num_starts_ho + sn.num_ft_conversions AS num_net_starts_ho,
-- Cancels (from settled events)
sm.num_cancels,
sm.num_cancels_ho,
sm.num_cancels_ft,
-- Free Trial Metrics (from spans)
sn.num_ft_conversions,
sn.num_ft_settled,
CASE WHEN sn.num_ft_settled > 0
THEN ROUND(100.0 * sn.num_ft_conversions / sn.num_ft_settled, 2)
ELSE 0
END AS ft_conversion_rate_pct,
-- Active Subscribers (from spans)
sn.num_active_period_subs,
sn.num_active_period_subs_ho,
sn.num_active_period_subs_ft,
-- End of Period Subscribers (from spans)
sn.num_end_period_subs,
sn.num_end_period_subs_ho,
sn.num_end_period_subs_ft
FROM settled_metrics sm
JOIN snapshot_metrics sn
ON sm.report_month = sn.report_month
AND sm.offer_marketplace = sn.offer_marketplace
ORDER BY sm.report_month, sm.offer_marketplace
Note: Replace your_subscription_events_table with your actual table name. Adjust the reporting_periods date range to match your analysis needs.