tl-openmeter-local-dev
OpenMeter Local Dev Setup
Run OpenMeter locally with full metering, billing, and webhook support. Covers the gotchas that differ between self-hosted (Docker) and Cloud (staging/production).
When to Use
- "Set up OpenMeter locally"
- "Configure ngrok for webhooks"
- "Debug catalog sync errors"
- "Why is my webhook 500ing?"
- "Install the Stripe app in OpenMeter"
- "What works locally vs staging?"
- When user mentions OpenMeter + Docker, local dev, or webhook issues
Suite
This skill is part of the tl-openmeter suite:
| Skill | Purpose |
|---|---|
| tl-openmeter-api | REST API reference (endpoints, schemas, gotchas) |
| tl-openmeter-local-dev | This skill: local dev setup and troubleshooting |
| tl-openmeter-api-mcp-server | MCP server for calling local OpenMeter from Cursor |
Resources
- references/REFERENCE.md — Env vars, Docker services, config files
- references/apps.md — OpenMeter Apps deep dive (Stripe, Sandbox, Custom)
- references/webhooks.md — Webhook auth modes, event types, testing
- scripts/verify-setup.ps1 — Verify local OpenMeter environment health
- assets/env-openmeter.template — Environment variable template
Step 0: Discover the User's Situation
Before proceeding, use the AskQuestion tool to determine what applies:
Question 1: Environment
- Local development (Docker) — proceed with this skill
- Staging or Production — redirect to
tl-openmeter-apiand deployment docs
Question 2: Stripe billing needed?
- Yes → Steps 1, 2, 3, 4, 5, 6 (full setup with ngrok)
- No → Steps 1, 4, 5 only (metering and entitlements without billing)
Question 3: Ngrok status (only if Stripe = yes)
- Already set up → skip ngrok install in Step 3
- Need to set it up → full Step 3
- Paid plan with static domain → note in Step 3 about skipping URL updates
Self-Hosted vs Cloud: Critical Differences
| Feature | Self-Hosted (Local) | Cloud (Staging/Prod) |
|---|---|---|
| Event ingestion | Yes | Yes |
| Meters, features, plans | Yes | Yes |
| Customers, subscriptions | Yes | Yes |
| Entitlement checks | Yes | Yes |
| Billing with Stripe App | Yes (via apps.baseURL) |
Yes |
| Webhook channels via API | NO ("not implemented") | Yes (Svix-backed) |
| Webhook notifications | Yes (YAML config only) | Yes (API channels) |
| Svix signature verification | No (plain HTTP) | Yes |
Key insight: Webhooks work locally via config.local.yaml static configuration. Cloud uses the POST /api/v1/notification/channels API (Svix). The catalog sync script handles this automatically.
Step 1: Start Docker Services
npm run docker:up
Verify OpenMeter is healthy:
curl http://localhost:8888/api/v1/meters
Portal UI: http://localhost:8889
Or run the verification script:
.\scripts\verify-setup.ps1
Step 2: Install Stripe App
See references/apps.md for full details on OpenMeter Apps.
npx tsx scripts/openmeter/openmeter-install-stripe-app.ts
This installs Stripe and creates a billing profile.
Requires: STRIPE_SECRET_KEY in .env
Verify: curl http://localhost:8888/api/v1/apps — should show Stripe (Sandbox may also be present and is irrelevant).
Step 3: Set Up Ngrok
Required for Stripe App callbacks and Stripe webhooks locally.
- Install:
choco install ngrok(Windows) orbrew install ngrok(macOS) - Auth:
ngrok config add-authtoken YOUR_TOKEN - Start:
.\scripts\start-ngrok.ps1orngrok http 3001 - Get URL:
curl http://127.0.0.1:4040/api/tunnels
Then configure (or use the helper script):
npx tsx scripts/openmeter/set-openmeter-webhook-url.ts
Manual config requires updating three places — see references/REFERENCE.md for the full list.
After config changes: docker compose restart openmeter
Step 4: Run Catalog Sync
npx tsx scripts/openmeter/openmeter-catalog-sync.ts
Expected local output includes ⊘ Skipping webhook channel (not supported on self-hosted OpenMeter) — this is normal.
See references/REFERENCE.md for common errors and fixes.
Step 5: Start API Server
npm run dev
For ngrok HTTP forwarding, set API_HTTP_FOR_NGROK=true in .env.
Step 6: Configure Stripe Webhooks
In Stripe Dashboard:
- Add endpoint:
https://YOUR-NGROK-URL/webhooks/stripe - Events:
checkout.session.completed,customer.subscription.updated,customer.subscription.deleted - Copy signing secret to
.envasSTRIPE_WEBHOOK_SECRET_DEV=whsec_...
Webhook Authentication
See references/webhooks.md for full details.
| Mode | Environment | Mechanism |
|---|---|---|
| Svix signatures | Cloud (staging/prod) | SVIX_WEBHOOK_SECRET + Svix headers |
| x-webhook-secret | Self-hosted with secret | OPENMETER_WEBHOOK_SECRET header check |
| Dev passthrough | Local (no secret set) | Accepts all — self-hosted sends plain HTTP |
Verification
Run the verification script to check all layers:
.\scripts\verify-setup.ps1
Or manually:
# OpenMeter running
curl http://localhost:8888/api/v1/meters
# Stripe App installed
curl http://localhost:8888/api/v1/apps
# Webhook endpoint responding
curl -X POST http://127.0.0.1:3001/webhooks/openmeter \
-H "Content-Type: application/json" \
-d '{"type":"entitlements.balance.threshold","payload":{"customerId":"test"}}'
Troubleshooting
| Symptom | Fix |
|---|---|
| EADDRINUSE on 3001 | Kill existing process on that port |
| OpenMeter container won't start | Check Docker logs; usually Kafka/PG not ready |
| Catalog sync connection refused | OpenMeter not running — docker ps |
| Stripe App missing after cleanup | npx tsx scripts/openmeter/openmeter-install-stripe-app.ts |
| Ngrok URL changed | Update .env, config.local.yaml, Stripe Dashboard; restart OpenMeter |
See references/REFERENCE.md for the full troubleshooting matrix.
References
First-Party Documentation
- OpenMeter Docker Setup — Official Docker guide
- OpenMeter Self-Hosted — Self-hosting guide
- Stripe App Setup — Stripe integration
Development Tools
- ngrok — Secure tunnels for webhook testing
- Stripe CLI — Local Stripe development
- Docker Compose — Multi-container orchestration
Related Skills
- tl-openmeter-api — REST API reference
- tl-openmeter-api-mcp-server — MCP server for Cursor
More from toddlevy/tl-agent-skills
tl-openmeter-api
Works with the OpenMeter REST API for usage metering, billing, and entitlements. Covers CloudEvents ingestion, meters, features, plans, customers, subscriptions, entitlements, notifications, billing profiles, invoices, apps, addons, grants, and the Stripe marketplace. Use when integrating OpenMeter, debugging metering, building catalog sync scripts, or when the user mentions OpenMeter API.
15tl-first-principles
Foundational software design principles traced to their intellectual origins. Covers information hiding, separation of concerns, abstraction, SSOT/DRY, conceptual integrity, and composition. Use when making architectural decisions, evaluating trade-offs, or understanding *why* best practices exist.
15tl-knip
Find and remove unused files, dependencies, and exports in TypeScript/JavaScript projects using Knip. Covers configuration-first workflow, plugin system, barrel file handling, CI integration, monorepo support, and agent-specific cleanup guidance.
14tl-docs-create
Create documentation from scratch for codebases. Covers SSOT-driven generation, writing standards, and templates for README/AGENTS.md/CHANGELOG. Use when creating new docs or documenting an undocumented codebase.
14tl-devlog
Maintain a structured development changelog (DEVLOG.md) capturing architectural decisions, milestones, incidents, and insights. Use when the user says "log this", "devlog", "archive this", or at natural pause points after significant decisions. Trigger on changelog, decision log, work log, or progress tracking.
14tl-docs-audit
Audit existing documentation for gaps, staleness, and sync issues. Generates sync reports with actionable findings. Use when reviewing doc coverage, finding outdated docs, or syncing docs with code.
14