# StripeMeter — pre-invoice parity for Stripe usage billing
[](https://github.com/geminimir/stripemeter/actions/workflows/ci.yml)
[](https://github.com/geminimir/stripemeter/releases)
[](https://opensource.org/licenses/MIT)
[](http://makeapullrequest.com)
[](https://github.com/geminimir/stripemeter/discussions)
[](https://github.com/geminimir/stripemeter/graphs/contributors)
**v0.4.0: Production-readiness pack**
See [v0.4.0 Release Notes](docs/RELEASE_NOTES_v0.4.0.md) and [Operator Runbook](RECONCILIATION.md).
[Open in Codespaces](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=geminimir/stripemeter)
· [5-min Quickstart](#quickstart)
· [Run the Parity Demo](docs/demos/parity-scenario-test-clocks.md)
· [Try the Stripe Test Clocks Demo](demo/stripe-test-clocks/README.md)
---
### What’s new in v0.4.0
- End-of-cycle **parity demo** (Stripe Test Clocks)
- **Replay API** — `POST /v1/replay` with dry-run/apply; watermark/cursor semantics
- **Shadow Mode** — test-environment pushes with deterministic idempotency keys
- **/metrics** + Prometheus + **Grafana** dashboard
- **ALERTS.md** + **RECONCILIATION.md** runbook
---
### Try in 5 minutes → Verify in 30 seconds
```bash
git clone https://github.com/geminimir/stripemeter && cd stripemeter
cp .env.example .env && docker compose up -d && pnpm -r build
pnpm db:migrate && pnpm dev
curl -fsS http://localhost:3000/health/ready | jq . || true
TENANT_ID=$(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid) bash examples/api-calls/send.sh
curl -fsS http://localhost:3000/metrics | head -n 30 # duplicate counted once
```
### What StripeMeter isn't
- Not a **pricing** or **entitlement** layer (use a pricing stack like Autumn; StripeMeter ensures usage **numbers are correct**).
- Not a data warehouse.
- Throughput targets: laptop p95 ingest ≤ **25 ms**, late-event replay (10k) ≤ **2 s**. Scale with queue/workers for higher volumes.
## What it is
A small service you run next to your app: it **dedupes** retries, handles **late events** with watermarks, keeps **running counters**, and **pushes only the delta** to Stripe so totals stay correct. A reconciliation loop + metrics catch drift before invoice close.
## Who it’s for
- SaaS teams on **Stripe usage-based pricing**
- Engineers who need **correct usage totals** and early **drift detection**
## What it is / isn’t
**It is**
- A **metering pipeline**: ingest → dedupe → aggregate → reconcile
- A **correctness guard** for Stripe usage billing (no surprise invoices)
- **Operator-ready**: `/health`, `/metrics`, drift tolerance, runbooks
**It isn’t**
- A payment processor or replacement for Stripe Billing
- A pricing engine/UI (those are optional extras; core is correctness)
---
## Quickstart
(Optional preflight: `bash scripts/preflight.sh`)
```bash
pnpm i -w
cp .env.example .env
docker compose up -d
pnpm -r build
pnpm db:migrate
pnpm dev
```
After services are up:
- Readiness: `GET http://localhost:3000/health/ready`
- Metrics: `GET http://localhost:3000/metrics`
- List events: `curl -s "http://localhost:3000/v1/events?tenantId=your-tenant-id&limit=10" | jq`
### Verify it worked (30-sec demo)
```bash
# 1) Health (should be healthy or degraded)
curl -fsS http://localhost:3000/health/ready | jq . || true
# 2) Idempotency demo: send the SAME event twice (counts once)
# TENANT_ID will be generated if unset
TENANT_ID=$(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid) bash examples/api-calls/send.sh
# 3) Check metrics (should reflect one accepted ingest)
curl -fsS http://localhost:3000/metrics | head -n 30
```
> If this clarified drift/idempotency, please ⭐ the repo and open an issue with what you tried — it guides the roadmap.
### Micro-proof numbers (optional quick check)
- p95 ingest latency: ~10–25 ms
- Re-aggregation of 10k late events: ≤ 2 s
- Duplicate events inside 24 h idempotency window: 0 double-counts
## Production checklist
- [x] Exact-once effect: idempotency window (duplicates won’t double-count)
- [x] Late events handled via watermarks + re-aggregation
- [x] Delta writes to Stripe (no over-reporting)
- [x] Health endpoints + structured logs + `/metrics`
- [x] Prometheus scrape + Grafana dashboard + alert recipes
- [x] Replay via API for safe reprocessing
- [x] Shadow Mode for safe test pushes
- [x] Triage & repair runbook with copy-paste commands
Reproduce locally:
```bash
# p95 for POST /v1/events/ingest (100 concurrent for 30s)
npx autocannon -m POST -H 'content-type: application/json' \
-b '{"events":[{"tenantId":"your-tenant-id","metric":"api_calls","customerRef":"c1","quantity":1,"ts":"2025-01-01T00:00:00Z"}]}' \
http://localhost:3000/v1/events/ingest
# Spot-check metrics after a short send
curl -s http://localhost:3000/metrics | grep -E "http_request_duration|process_" || true
```
### Configure metrics (optional)
Put a tiny config in `examples/config/stripemeter.config.ts` to map `metric → counter` and choose a `watermarkWindowSeconds`.
### Shadow Mode
Shadow Mode lets you post usage to Stripe’s test environment in parallel without affecting live invoices.
- Set `STRIPE_TEST_SECRET_KEY` in your environment (in addition to `STRIPE_SECRET_KEY`).
- Mark a price mapping with `shadow=true` and provide `shadowStripeAccount`, `shadowPriceId`, and optionally `shadowSubscriptionItemId`.
- The writer routes these to the Stripe test client and uses deterministic idempotency keys.
- Live invoices remain unaffected; live write logs are not updated for shadow pushes.
- Metrics: `shadow_usage_posts_total`, `shadow_usage_post_failures_total`.
- Guardrails: if `shadow=true` but `STRIPE_TEST_SECRET_KEY` is missing, pushes are skipped with warnings.
### Pick your case (examples)
- API calls: `bash examples/api-calls/verify.sh`
- Seats: `bash examples/seats/verify.sh`
Each script checks health, sends a duplicate event with an explicit idempotency key, and prints the first lines of `/metrics` so you can see it counted once.
**StripeMeter** is a Stripe-native usage metering system focused on correctness and operability. Built by developers who believe customers should be able to verify what they’re billed for.
## Why StripeMeter?
- Correct usage totals via idempotent ingest and late-event handling
- Pre-invoice parity within ε, monitored by alerts; see the runbook
- Fresh counters and delta pushes to Stripe to avoid over-reporting
- Operator-grade: health, metrics, dashboards, and alert recipes
## What Makes StripeMeter Special
Unlike other billing solutions, StripeMeter is designed around three core principles:
1. **Transparency First**: Customers should never be surprised by their bill
2. **Developer Experience**: Building usage-based pricing should be delightful
3. **Community Driven**: Built by the community, for the community
> **Adopters wanted (2 slots this week).**
> If you run Stripe usage-based pricing, I’ll pair for 30 minutes to wire one meter in staging or set up a nightly replay. You’ll get priority on your must-have knobs and a thank-you in the next release notes.
> → Open a discussion: “Staging adopter” or DM via the repo email.
## Architecture
```
┌─────────────┐ ┌──────────┐ ┌──────────────┐
│ Clients │────▶│ Ingest │────▶│ Events │
│ (SDK/HTTP) │ │ API │ │ (Postgres) │
└─────────────┘ └──────────┘ └──────────────┘
│ │
▼ ▼
┌──────────┐ ┌──────────────┐
│ Queue │────▶│ Aggregator │
│ (Redis) │ │ Worker │
└──────────┘ └──────────────┘
│
▼
┌──────────────┐
│ Counters │
│(Redis + PG) │
└──────────────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Stripe │ │ Alerts │ │ Customer │
│ Writer │ │ & Caps │ │ Widget │
└──────────┘ └──────────┘ └──────────┘
```
## Project Structure
```
stripemeter/
├── packages/
│ ├── core/ # Shared types, schemas, utilities
│ ├── database/ # Database layer (Drizzle ORM + Redis)
│ ├── pricing-lib/ # Pricing calculation engine
│ ├── sdk-node/ # Node.js SDK
│ └── sdk-python/ # Python SDK
├── apps/
│ ├── api/ # REST API (Fastify)
│ ├── workers/ # Background workers (BullMQ)
│ ├── admin-ui/ # Admin dashboard (React)
│ └── customer-widget/ # Embeddable widget (React)
└── infra/ # Infrastructure configs
```
## Quick Start
**Get StripeMeter running in under 5 minutes**
### One-Command Setup
```bash
# Clone and setup everything automatically
git clone https://github.com/geminimir/stripemeter.git
cd stripemeter && ./scripts/setup.sh
```
That's it! The setup script will:
- Check prerequisites (Node.js 20+, pnpm, Docker)
- Install dependencies
- Start infrastructure services
- Run database migrations
- Create example configuration
### Manual Setup (if you prefer)
Click to expand manual installation steps
1. **Prerequisites**: Node.js 20+, pnpm 8+, Docker
2. **Install**: `pnpm install`
3. **Configure**: Copy `.env.example` to `.env` and add your Stripe keys
4. **Infrastructure**: `docker compose up -d`
5. **Database**: `pnpm db:migrate`
6. **Start**: `pnpm dev`
Node.js SDK
```javascript
import { createClient } from '@stripemeter/sdk-node';
const client = createClient({
apiUrl: 'http://localhost:3000',
tenantId: 'your-tenant-id',
customerId: 'cus_ABC123'
});
// Track a single event
await client.track({
metric: 'api_calls',
customerRef: 'cus_ABC123',
quantity: 100,
meta: { endpoint: '/v1/search', region: 'us-east-1' }
});
// Get live usage and cost projection
const usage = await client.getUsage('cus_ABC123');
const projection = await client.getProjection('cus_ABC123');
console.log(`Current usage: ${usage.metrics[0].current}`);
console.log(`Projected cost: $${projection.total}`);
```
Python SDK
```python
from stripemeter import StripeMeterClient
client = StripeMeterClient(
api_url="http://localhost:3000",
tenant_id="your-tenant-id",
customer_id="cus_ABC123"
)
# Track usage
client.track(
metric="api_calls",
customer_ref="cus_ABC123",
quantity=100,
meta={"endpoint": "/v1/search", "region": "us-east-1"}
)
# Get projections
projection = client.get_projection("cus_ABC123")
print(f"Projected cost: ${projection.total}")
```
REST API
```bash
# Ingest usage events
curl -X POST http://localhost:3000/v1/events/ingest \
-H "Content-Type: application/json" \
-d '{
"events": [{
"tenantId": "your-tenant-id",
"metric": "api_calls",
"customerRef": "cus_ABC123",
"quantity": 100,
"ts": "2025-01-16T14:30:00Z"
}]
}'
# Get cost projection
curl -X POST http://localhost:3000/v1/usage/projection \
-H "Content-Type: application/json" \
-d '{"tenantId": "your-tenant-id", "customerRef": "cus_ABC123"}'
```