SubTracker

How do you store subscription price history and detect price increases?

Price history as an append-only event log with old and new amounts, plus an idempotent price-hike alert job. Notes from a production schema.

Answer

Store every change as a row in an append-only events table with the old and new amount, rather than overwriting the price on the subscription. A price increase is then an event of type price_changed. An alert job reads events that have not yet been alerted, sends the warning and stamps an alerted-at timestamp, which makes retries safe and keeps a complete price history for reports.

LM

Leutrim Miftaraj

Founder, SubTracker · Updated September 29, 2026

The short answer

Store every change as a row in an append-only events table with the old and new amount, rather than overwriting the price on the subscription. A price increase is then an event of type price_changed. An alert job reads events that have not yet been alerted, sends the warning and stamps an alerted-at timestamp, which makes retries safe and keeps a complete price history for reports.

Overwriting Loses the Story

The simplest schema stores the current price on the subscription and updates it in place. It answers "what does this cost now" and nothing else: not when the price changed, by how much, or how often. Those are exactly the questions a user asks when deciding whether a subscription is still worth it.

An Append-Only Event Log

SubTracker records changes in a `subscription_events` table: one row per event, with an event type — created, edited, cancelled, reactivated, price_changed, status_changed — and columns for old and new amount and old and new status. The subscription row still holds the current price; the events table holds how it got there.

Because rows are only ever inserted, the history cannot be rewritten by a later edit, and any report can be rebuilt from it.

Price Increases as Events

When a user edits the amount of a subscription, the application writes a `price_changed` event with both values. An increase is simply an event where the new amount exceeds the old. Month-on-month reports read the same events to explain why spending moved, instead of inferring it from two snapshots.

An Idempotent Alert Job

Price-hike alerts are sent by a scheduled job that selects `price_changed` events with no `alerted_at` value, sends the email and stamps the timestamp. A partial index on event type and alerted-at keeps that lookup cheap as the table grows.

The stamp is what makes the job safe to retry: an event is alerted once, however many times the job runs or crashes mid-batch.

What This Design Does Not Do

It records the prices the user enters. It does not discover price changes by itself — there is no bank connection to observe them. The event log makes the most of the data it is given; it does not invent data it was never given.

Frequently asked questions

Why not just update the price on the subscription?+

Because it discards the history. You lose when the price changed, by how much and how often — the information that decides whether a subscription is still worth keeping.

How does the price-hike alert avoid sending twice?+

Each price_changed event gets an alerted_at timestamp once its alert is sent. The job only selects events without one, so retries and crashes cannot send duplicates.

Can the event log power spending reports?+

Yes. Month-on-month comparisons read the same events to explain changes in spending, rather than comparing two snapshots and guessing the reason.

Does this detect price increases automatically?+

Only for prices that are entered into the tracker. Without a bank connection, the system records the changes it is told about; it cannot observe charges on its own.