When engineering teams build subscription billing systems, the immediate priority is functional: verifying receipts, granting entitlements, and unlocking features for the user. However, data models built strictly for feature gating often prove disastrous for financial reporting and cohort analytics.
In this guide, we outline the foundational data architecture required to maintain clean, auditable subscription telemetry across web and mobile platforms.
1. Never Store Subscription State as a Single Enum
A common anti-pattern in database design is maintaining a single status column on a users table:
-- ANTI-PATTERN: Single mutable status column
ALTER TABLE users ADD COLUMN subscription_status VARCHAR(50);
-- e.g. 'active', 'canceled', 'expired'
When a user cancels their subscription, updating this single column immediately destroys historical timeline data. You can no longer answer:
- When was the initial conversion date?
- Was the user ever in a billing retry state?
- Did they downgrade from an Annual plan to a Monthly plan mid-cycle?
The Solution: An Immutable Event Ledger
Instead, maintain an immutable event log for every subscription state change:
CREATE TABLE subscription_events (
event_id UUID PRIMARY KEY,
user_id UUID NOT NULL,
original_transaction_id VARCHAR(255) NOT NULL,
event_type VARCHAR(100) NOT NULL, -- INITIAL_PURCHASE, RENEWAL, REFUND, UPGRADE
plan_id VARCHAR(100) NOT NULL,
currency VARCHAR(3) NOT NULL,
gross_amount DECIMAL(10, 2) NOT NULL,
net_proceeds DECIMAL(10, 2) NOT NULL,
storefront_country VARCHAR(2) NOT NULL,
effective_timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
expiration_timestamp TIMESTAMP WITH TIME ZONE NOT NULL
);
2. Anchor Cohorts to the Original Transaction ID
In iOS StoreKit and Google Play Billing, every subscription lifecycle begins with an immutable identifier (e.g. original_transaction_id in Apple’s ecosystem).
Even if a user cancels, renews three months later, upgrades their plan, or switches from monthly to annual billing, all subsequent transactions link back to this single root ID.
By anchoring your cohort analytics to original_transaction_id rather than ephemeral transaction tokens, your analytics engine can accurately reconstruct subscriber lifetime value across multi-year intervals without double-counting renewals as new user conversions.
3. Implement Server-Side Webhook Idempotency
Both Apple and Google deliver server-to-server notifications with “at least once” delivery guarantees. This means your backend will occasionally receive duplicate webhooks for the exact same renewal event.
Without an idempotency table verifying transaction identifiers, your internal telemetry will record duplicate renewal revenue, leading to inflated financial metrics and inaccurate cohort retention curves.