Managed OpenClaw agent computer instances with per-instance billing via Stripe and AI token metering via Vercel AI Gateway.
User -> Next.js Dashboard (agentcomputers.app)
|
+-> Stripe Checkout (per-instance subscription)
+-> Hetzner Cloud API (VPS provisioning)
+-> Supabase (auth + database)
+-> Vercel AI Gateway (LLM routing + token metering)
|
+-> Stripe Billing Meters (usage-based billing)
Each instance gets its own Stripe subscription with:
- A fixed monthly compute fee (based on plan tier)
- Metered AI token usage billed at the end of each billing cycle
- Framework: Next.js 16 (App Router, Turbopack)
- Auth: Supabase Auth
- Database: Supabase Postgres + Drizzle ORM
- Payments: Stripe (Checkout, Subscriptions, Billing Meters)
- Infrastructure: Hetzner Cloud (VPS), Cloudflare DNS
- AI Gateway: Vercel AI Gateway with Stripe metering headers
- UI: shadcn/ui, Tailwind CSS, Recharts
- Testing: Vitest
- CI: GitHub Actions
| Plan | Price | Specs | Hetzner Type |
|---|---|---|---|
| Starter | 5.99 EUR/mo | 2 vCPU, 4 GB RAM | cx23 |
| Pro | 9.99 EUR/mo | 4 vCPU, 8 GB RAM | cx33 |
| Business | 17.99 EUR/mo | 8 vCPU, 16 GB RAM | cx43 |
Plus metered AI token usage on top.
- Node.js 22+
- A Supabase project
- A Stripe account
- A Hetzner Cloud account
- (Optional) Cloudflare account for DNS
npm installcp .env.example .env.localFill in all values. See .env.example for the full list.
This creates the product, plan prices, billing meter, metered price, and webhook in one command:
STRIPE_SECRET_KEY=sk_test_YOUR_KEY npx tsx scripts/sync-stripe-products.ts http://localhost:3000/api/webhooks/stripePaste the output values into your .env.local.
npx supabase db pushOr if using Drizzle:
npm run db:pushnpm run devIn a separate terminal:
stripe listen --forward-to localhost:3000/api/webhooks/stripeCopy the whsec_... signing secret it prints and set it as STRIPE_WEBHOOK_SECRET in .env.local.
npm test # single run
npm run test:watch # watch mode
npm run test:coverage # with coverageLocal development uses Stripe test mode by default. To test the full payment flow:
- Make sure
.env.localhassk_test_...keys (notsk_live_...) - Start the Stripe webhook listener:
stripe listen --forward-to localhost:3000/api/webhooks/stripe - Start the dev server:
npm run dev - Create an instance in the UI
- At Stripe Checkout, use test card 4242 4242 4242 4242, any future expiry, any CVC
- Payment succeeds instantly, the webhook fires, and the instance starts provisioning
Other useful test cards:
4000 0000 0000 3220-- triggers 3D Secure authentication4000 0000 0000 0002-- always declines
If you need to create test Stripe resources from scratch:
# Set your test secret key and run the setup script
STRIPE_SECRET_KEY=sk_test_YOUR_KEY npx tsx scripts/sync-stripe-products.ts http://localhost:3000/api/webhooks/stripeThis creates:
- A product ("Agent Computer Instance")
- Three plan prices (Starter, Pro, Business)
- A billing meter (
token-billing-tokens) - A metered price for AI token usage
- A webhook endpoint
The script prints all the env vars you need -- paste them into .env.local.
The CI pipeline runs on every push to master and every PR:
- Lint (
eslint) - Typecheck (
tsc --noEmit) - Tests (
vitest run)
- Push to
masterdeploys to production (agentcomputers.app) with live Stripe keys - PRs create preview deployments
To set up preview deployments with test Stripe:
-
Run the setup script with your preview URL:
STRIPE_SECRET_KEY=sk_test_YOUR_KEY npx tsx scripts/sync-stripe-products.ts https://YOUR-PREVIEW-URL.vercel.app/api/webhooks/stripe
-
In the Vercel dashboard, add the test env vars to the Preview environment:
STRIPE_SECRET_KEY=sk_test_...STRIPE_WEBHOOK_SECRET= output from step 1STRIPE_PRICE_STARTER,STRIPE_PRICE_PRO,STRIPE_PRICE_BUSINESS= output from step 1STRIPE_PRICE_TOKEN_USAGE,STRIPE_METER_ID= output from step 1
-
Production env vars (live keys) remain unchanged.
| Variable | Description |
|---|---|
STRIPE_SECRET_KEY |
Stripe API key (sk_test_... or sk_live_...) |
STRIPE_WEBHOOK_SECRET |
Webhook signing secret (whsec_...) |
STRIPE_RESTRICTED_ACCESS_KEY |
Restricted key for AI Gateway meter events (rk_live_...) |
STRIPE_PRICE_STARTER |
Stripe price ID for Starter plan |
STRIPE_PRICE_PRO |
Stripe price ID for Pro plan |
STRIPE_PRICE_BUSINESS |
Stripe price ID for Business plan |
STRIPE_PRICE_TOKEN_USAGE |
Stripe price ID for metered token usage |
STRIPE_METER_ID |
Stripe billing meter ID |
HETZNER_API_TOKEN |
Hetzner Cloud API token |
HETZNER_SSH_KEY_ID |
Hetzner SSH key ID for server access |
VERCEL_AI_GATEWAY_KEY |
Vercel AI Gateway API key |
INSTANCE_DOMAIN |
Domain for instance dashboards (e.g. agentcomputers.app) |
DATABASE_URL |
Postgres connection string |
| Command | Description |
|---|---|
npm run dev |
Start dev server with Turbopack |
npm run build |
Production build |
npm test |
Run unit tests |
npm run lint |
Run ESLint |
npm run stripe:setup |
Create Stripe products/prices/meter |
npm run seed |
Seed dev data |
npm run db:push |
Push Drizzle schema to database |
npm run db:studio |
Open Drizzle Studio |
1. User creates instance -> POST /api/instances
2. API creates instance (status: pending_payment) + Stripe Checkout Session
3. User pays at Stripe Checkout
4. Stripe fires checkout.session.completed webhook
5. Webhook transitions instance to "provisioning" (idempotent)
6. provisionInstance() creates Hetzner VPS with OpenClaw + AI Gateway config
7. Instance goes "running" -- AI calls routed through Vercel AI Gateway
8. Gateway emits Stripe meter events with customer's stripe-customer-id
9. Stripe accumulates token usage, charges at end of billing cycle