Inspiration

Streaming is the last big consumption medium still sold like an all-you-can-eat buffet. You pay $11.99/month flat, and your money gets thrown into a giant pool that's split by share of total platform streams — so a song you loved and a song you skipped pay out the same way, and most of your fee funds whoever is trending rather than who you actually listened to.

That's broken in two directions:

  1. Listeners overpay. The average listener plays ~3,278 songs/year ≈ 9,800 minutes. At a fair per-minute rate that's about $8/monthless than the flat fee. Light listeners subsidize heavy ones.
  2. Artists are paid by a pool, not by you. Per-stream payout is an opaque, ever-shrinking slice of a shared pot. There's no clean line from "I listened to this track for 4 minutes" to "this artist earned for those 4 minutes."

Usage-based billing already won cloud, APIs, and AI tokens. We wanted to apply that same metered-billing pattern to music: you pay for the minutes you actually listen, and artists get paid for the minutes you actually played.

What it does

TollRoad meters playback the way a utility meters electricity. Every minute streamed is a metered billing event attributed to one listener and one rightsholder.

  • Listeners top up a prepaid balance, stream anything, and watch a live per-minute taximeter tick up as they play. Stop listening, stop paying. A hard stop-at-zero gate means you can never stream past your balance.
  • Artists set their own per-track rate — anywhere from free to $1.00/min at 0.1¢ precision — and earn on real consumption, settled into an auditable royalty ledger and paid out via Stripe Connect.
  • Labels / catalogs get the same engine as infrastructure — "Stripe for music royalties."
  • AI agents are first-class clients. An MCP server turns the catalog into a programmatic Vibe DJ that discovers, pays (via an x402-style protocol), and streams on your behalf.

On top of the metering core we shipped a full product: wallet + Stripe top-ups, likes/playlists/library (liking a track costs one real minute — a money-backed signal), Superfan Bonds (every paid minute becomes a ranked, un-fakeable relationship with an artist), a Vibe Pad mood-tagging mini-game that pays out free minutes and produces an AI-tagging training set, vector-based discovery, and shareable artist/profile pages.

How we built it

The heart of the project is a polyglot CQRS architecture that uses two AWS databases, each for the grain it's best at:

  • Amazon DynamoDB — the command / hot path. Holds the listener's authoritative real-time balance via a conditional UpdateItem that debits only while balance ≥ cost (so you physically can't stream past zero), plus the METER/TOPUP event firehose. Every metered minute is a single TransactWriteItems — conditional debit + guarded event write — chosen for single-digit-ms conditional writes and the scale to handle ~16–23K writes/sec at a million concurrent streams.
  • Amazon Aurora DSQL — the query side / system of record. Serverless, scale-to-zero, Postgres-compatible. Holds the catalog, accounts, library, and the append-only royalty_ledger (one immutable credit row per metered minute), plus precomputed per-artist/day summaries. Superfan bonds and leaderboards are derived in-SQL — no extra store. Kept out of the hot loop so it can scale to zero between projection runs.
  • AWS Lambda — the projector. The sole writer of the DSQL read models. It consumes DynamoDB Streams (at-least-once, duplicates expected) and builds the ledger idempotently (UNIQUE key + ON CONFLICT DO NOTHING) with OCC 40001 retries.
  • S3 + CloudFront + KMS for delivery: audio is SSE-KMS encrypted, served via Origin Access Control behind short-TTL (150s) signed URLs the API issues only after the meter authorizes the minute.
  • Frontend on Vercel, built with v0 — the listener player with its live meter and the artist dashboard.
  • Stripe for top-ups and Stripe Connect (Express) for artist payouts, fed by the DSQL ledger.

Streaming is gated by an x402-style protocolrequest → 402 Payment Required → pay → retry — but crypto-free: settlement is the prepaid wallet, not a chain. That makes the whole thing API-first and agent-friendly out of the box.

Challenges we ran into

  • Designing around Aurora DSQL's grain. DSQL isn't vanilla Postgres: no foreign keys, no triggers, no PL/pgSQL, optimistic concurrency (no row locks), a 128 MiB/query limit, and per-transaction caps. A mutable "running balance" row would have thrown 40001 under load — so we made the ledger append-only and compute balances as SUM/summaries, which is both OCC-friendly and the correct accounting pattern. BI runs off precomputed summaries, never a heavy scan.
  • A real CQRS race we had to fix by construction. Originally the synchronous charge wrote the DSQL ledger itself, front-running the projector — so the projector always hit ON CONFLICT DO NOTHING and the rollup summaries never updated. The fix: the command path touches only DynamoDB, and the projector owns DSQL. No race, no dropped summary.
  • Sub-cent pricing. To represent a 0.5¢/min track, everything is stored in millicents (cents × 1000); Stripe stays in whole cents at the boundary and the remainder lives in the artist balance.
  • Server-authoritative metering. Playback depends on the meter, not the reverse: no recent paid minute → HTTP 402, and the signed URL expires within 150s. You can't listen free, and you can't wash-stream to inflate earnings.

What we learned

  • Polyglot persistence pays off when each database is matched to its access pattern — DynamoDB for high-velocity conditional writes, Aurora DSQL for relational reads and an auditable system of record — joined by a stream-driven projector.
  • CQRS forces a clean ownership model: one writer per store removes whole classes of race conditions.
  • Designing into a database's constraints (append-only ledgers, derived-in-SQL leaderboards, scale-to-zero) produces a cleaner system than fighting them.

What's next

A server-authoritative signed-heartbeat meter (the server issues a signed token chain and computes elapsed time itself) to fully close client-side forgery, deeper label/catalog onboarding, and expanding the agent/MCP ecosystem around the public /v1 API.

Built With

Share this project:

Updates