Skip to content

Stripe Token Billing in 10 minutes

Bill your end-clients straight from the gateway: every finalized usage record of a mapped client is exported to Stripe Billing meter events under the event name sociaro.usage. You attach a price to that meter in Stripe, Stripe aggregates and invoices — the gateway just delivers accurate, deduplicated usage.

  • Auth: Authorization: Bearer gw_live_…; scopes per endpoint below.
  • Tenant-scoped: your organization is derived from the key.
  • Errors use the standard envelope.
  1. A Stripe account with Billing Meters available (Token Billing / usage-based billing).
  2. A restricted Stripe API key (rk_…, recommended) with write access to Billing meter events and Customers — or a secret key (sk_live_…). The gateway uses it for exactly two calls: POST /v1/billing/meter_events and POST /v1/customers.
  3. A meter in Stripe with event name sociaro.usage aggregating payload.value (the cost in USD) per customer.
  4. Gateway-side (operator): the stripe_export config section enabled and a valid GATEWAY_CRED_ENCRYPTION_KEY — per-client Stripe keys are stored AES-256-GCM encrypted with the same master key as upstream credentials. On the hosted gateway this is already set up.

Everything below is available in the dashboard under Stripe Billing:

  1. Connect your key — paste the restricted key, toggle Enable export, save. The key is stored encrypted and never displayed again (the API returns only enabled/configured flags — write-only by design).
  2. Map clients to customers — for each of your clients (sub-accounts), either click Create in Stripe (the gateway creates a Stripe customer with metadata[sub_account_id] set and stores the mapping) or link an existing cus_… id. Only mapped clients are exported.
  3. Watch the export status — pending/sent/failed counters, the recent event list and a Retry button for parked events.
  • Transactional outbox. The export event is enqueued in the same database transaction that commits the usage record — an event can never exist without its usage row, and committed usage of a mapped client can never miss the queue. A background poller drains the outbox; Stripe being slow or down never touches the request path.
  • Exactly-once. Each meter event carries identifier = usage_record_id. Stripe deduplicates on it, so at-least-once delivery (and manual retries) can never double-bill.
  • Async jobs are billed at final cost. Provisional records of async jobs — any async kind: video, async image, async ASR (audio), and 3d once enabled — are not exported; the event is enqueued by finalization with the final cost (async jobs). Synchronous requests (including the synchronous /v1/images/generations door) are exported like any other finalized record.
  • Retries with backoff. Failed sends retry with exponential backoff (30s doubling up to 1h). After 10 attempts the event is parked as failed — it is never lost, and the Retry button (or POST …/retry) re-queues it.
  • What is NOT exported: zero-cost records; records of keys not bound to a sub-account (your org-level keys); provisional async records; anything while the export is disabled or the client is unmapped (those records are simply never enqueued — disabling is not retroactive).

The meter event payload: payload[stripe_customer_id], payload[value] (cost in USD), payload[provider], payload[model], plus the record’s timestamp. payload[provider] is the catalog vendor (e.g. bytedance, alibaba) — the same value GET /gw/stripe/events reports; the gateway’s internal routing provider is never sent to Stripe.

EndpointScopeDescription
GET /gw/stripe/configstats:read{"enabled", "configured", "mappings": [{"sub_account_id", "stripe_customer_id"}]} — the API key is never returned
PUT /gw/stripe/configkeys:manageBody {"enabled": bool, "api_key": "rk_…"?}. With api_key — stores the key encrypted; without — toggles enabled only (400 when enabling with no key configured). 204
PUT /gw/stripe/mappings/{sub_account_id}keys:manageBody {"stripe_customer_id": "cus_…"} — link to an existing customer. 204; 404 for a foreign/unknown sub-account
DELETE /gw/stripe/mappings/{sub_account_id}keys:manageUnlink (stops new exports for that client). 204 / 404
POST /gw/stripe/customerskeys:manageBody {"sub_account_id", "name"?} — creates a Stripe customer with your key and stores the mapping. Returns {"stripe_customer_id"}; Stripe errors → 502
GET /gw/stripe/eventsstats:readQuery status=pending|sent|failed, limit (default 50, max 200) → {"counts": {"pending","sent_24h","failed"}, "oldest_pending_age_seconds", "events": […]}
POST /gw/stripe/events/{usage_record_id}/retrykeys:manageRe-queue a parked failed event (attempts reset). 204; 404 if not found / not yours / not failed

Example — connect and check:

Terminal window
curl -X PUT https://api.sociaro.com/gw/stripe/config \
-H "Authorization: Bearer $GW_KEY" -H "Content-Type: application/json" \
-d '{"enabled": true, "api_key": "rk_live_..."}'
curl -H "Authorization: Bearer $GW_KEY" https://api.sociaro.com/gw/stripe/config
# {"configured":true,"enabled":true,"mappings":[...]} ← no key material, ever
  • Events stuck in pending — the export is enabled? (enabled: false pauses sending; the backlog waits and drains once re-enabled.) Check oldest_pending_age_seconds in GET /gw/stripe/events.
  • Events failedlast_error carries the (truncated) Stripe response. Typical causes: revoked/insufficient key (401/403 — reconnect a key with meter-events write access), deleted customer, meter not configured for sociaro.usage.
  • Parked after 10 attempts — fix the cause, then hit Retry (or POST /gw/stripe/events/{usage_record_id}/retry). Retrying an event that did reach Stripe is harmless — deduplicated by identifier.
  • A client’s usage is missing — the client must be mapped before the usage happens: enqueueing is decided at usage-commit time and is not retroactive. Zero-cost and org-level-key records are never exported.