Subscription Cancellation Reason
Also known as: Cancellation Reason, Subscription Churn Reason, Cancel Reason, Churn Reason, cancellation_reason
Reason a subscriber picks in the cancel flow when ending a recurring product subscription, e.g. too_expensive, too_much_product, no_longer_needed. Splits voluntary subscription churn by cause.
Business Use Case
1️⃣ Product: when too_much_product leads cancellations, adds a change-frequency save offer to the cancel flow. 2️⃣ Marketing: when too_expensive share rises after a price change, tests a deeper subscribe-and-save discount tier. 3️⃣ Merchandising: when product_quality cancellations cluster on one SKU, reviews the product with the supplier. 4️⃣ Customer Service: when delivery_issue cancellations rise, escalates the carrier and contacts at-risk subscribers.
Business Criticality
High
Journey / Navigation Area
Engagement
Owning Function
Marketing
Scope
Event
Calculation Notes
1️⃣ Source: cancellation_reason on submit_form (form_type = subscription_cancellation), keyed by subscription_id; one value per cancellation. 2️⃣ Values: too_expensive, too_much_product, no_longer_needed, product_quality, delivery_issue, switched_provider, other. Single-select only. 3️⃣ Send only on confirmed cancellation; subscribers who accept a save offer (skip, pause, discount) have not cancelled and send no reason. 4️⃣ Dunning cancellations run in the billing backend and never fire this submit_form; out of scope and excluded from reconciliation. 5️⃣ Merchant- or agent-initiated cancellations bypass the cancel flow and are excluded likewise; single-order cancellations are out of scope. 6️⃣ Free-text 'other' comments stay in the billing platform (Recharge, Recurly, Chargebee); never send them to analytics, as they may hold PII. 7️⃣ Reconcile weekly to the billing platform's voluntary cancellation log by subscription_id; a cancellation with no reason is a tagging gap.
Technical Details
Events
| Platform | Event |
|---|---|
| Google Analytics 4 | submit_form |
| Adobe | form_submit |
Data Mappings
W3 Data Layer
{
"event": [
{
"eventInfo": {
"eventName": "submit_form"
},
"attributes": {
"form_type": "subscription_cancellation",
"subscription_id": "SUB-1",
"cancellation_reason": "too_expensive"
}
}
]
}GA4 Data Layer
{
"event": "submit_form",
"form_type": "subscription_cancellation",
"subscription_id": "SUB-1",
"cancellation_reason": "too_expensive"
}Adobe Client Data Layer
{
"event": "form_submit",
"eventInfo": {
"form_type": "subscription_cancellation",
"subscription_id": "SUB-1",
"cancellation_reason": "too_expensive"
}
}XDM Mapping
{
"eventType": "web.formFilledOut",
"_tenant": {
"form_type": "subscription_cancellation",
"subscription_id": "SUB-1",
"cancellation_reason": "too_expensive"
}
}SQL Queries
Google Analytics 4 (BigQuery)
-- ==========================================
-- 1. GOOGLE BIGQUERY SQL
-- ==========================================
-- Relative reporting window: last 90 complete days
DECLARE start_date DATE DEFAULT DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY);
DECLARE end_date DATE DEFAULT DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY);
-- Subscription-cancellation submit_form events with subscription_id and cancellation_reason pivoted from event_params
WITH cancel_events AS (
SELECT
event_timestamp,
(SELECT COALESCE(ep.value.string_value, CAST(ep.value.int_value AS STRING)) FROM UNNEST(event_params) ep WHERE ep.key = 'subscription_id') AS subscription_id,
(SELECT ep.value.string_value FROM UNNEST(event_params) ep WHERE ep.key = 'cancellation_reason') AS cancellation_reason
FROM `your-project.analytics_123456789.events_*`
WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', start_date) AND FORMAT_DATE('%Y%m%d', end_date)
AND event_name = 'submit_form'
AND (SELECT ep.value.string_value FROM UNNEST(event_params) ep WHERE ep.key = 'form_type') = 'subscription_cancellation'
),
-- One value per cancellation: keep the last submit per subscription_id
cancellations AS (
SELECT
subscription_id,
cancellation_reason,
ROW_NUMBER() OVER (PARTITION BY subscription_id ORDER BY event_timestamp DESC) AS rn
FROM cancel_events
WHERE subscription_id IS NOT NULL
)
-- Reason per cancellation; a missing reason is reported as (not set) to flag tagging gaps
SELECT
subscription_id,
COALESCE(cancellation_reason, '(not set)') AS cancellation_reason
FROM cancellations
WHERE rn = 1;Adobe Experience Platform (AEP Query Service)
-- ==========================================
-- 2. ADOBE AEP QUERY SERVICE SQL
-- ==========================================
-- Relative reporting window: last 90 complete days
WITH params AS (
SELECT
CAST(DATE_SUB(CURRENT_DATE, 90) AS DATE) AS start_date,
CAST(DATE_SUB(CURRENT_DATE, 1) AS DATE) AS end_date
),
-- Subscription-cancellation form submits with subscription_id and cancellation_reason
cancel_events AS (
SELECT
w.timestamp AS event_ts,
w._tenant.subscription_id AS subscription_id,
w._tenant.cancellation_reason AS cancellation_reason
FROM aep_web_events_dataset w
CROSS JOIN params p
WHERE w.eventType = 'web.formFilledOut'
AND w._tenant.form_type = 'subscription_cancellation'
AND CAST(w.timestamp AS DATE) BETWEEN p.start_date AND p.end_date
),
-- One value per cancellation: keep the last submit per subscription_id
cancellations AS (
SELECT
subscription_id,
cancellation_reason,
ROW_NUMBER() OVER (PARTITION BY subscription_id ORDER BY event_ts DESC) AS rn
FROM cancel_events
WHERE subscription_id IS NOT NULL
)
-- Reason per cancellation; a missing reason is reported as (not set) to flag tagging gaps
SELECT
subscription_id,
COALESCE(cancellation_reason, '(not set)') AS cancellation_reason
FROM cancellations
WHERE rn = 1;Source Data
Digital Analytics (GA4)
Dependencies
Prerequisite: List the dependencies required for this Dimension
Dimensions:
Report Attributes
Subscription Status Customer Tenure Band Item Category Device Category Traffic Source
Segment Eligibility
true
Governance
Governance
GitHub
catalog/dimensions/subscription-cancellation-reason.yml