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.
Prerequisites
Section titled “Prerequisites”- A Stripe account with Billing Meters available (Token Billing / usage-based billing).
- 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_eventsandPOST /v1/customers. - A meter in Stripe with event name
sociaro.usageaggregatingpayload.value(the cost in USD) per customer. - Gateway-side (operator): the
stripe_exportconfig section enabled and a validGATEWAY_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.
Dashboard flow
Section titled “Dashboard flow”Everything below is available in the dashboard under Stripe Billing:
- 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/configuredflags — write-only by design). - 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 existingcus_…id. Only mapped clients are exported. - Watch the export status — pending/sent/failed counters, the recent event list and a Retry button for parked events.
How the export works
Section titled “How the export works”- 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, asyncimage, async ASR (audio), and3donce enabled — are not exported; the event is enqueued by finalization with the final cost (async jobs). Synchronous requests (including the synchronous/v1/images/generationsdoor) 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 (orPOST …/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.
API reference
Section titled “API reference”| Endpoint | Scope | Description |
|---|---|---|
GET /gw/stripe/config | stats:read | {"enabled", "configured", "mappings": [{"sub_account_id", "stripe_customer_id"}]} — the API key is never returned |
PUT /gw/stripe/config | keys:manage | Body {"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:manage | Body {"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:manage | Unlink (stops new exports for that client). 204 / 404 |
POST /gw/stripe/customers | keys:manage | Body {"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/events | stats:read | Query 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}/retry | keys:manage | Re-queue a parked failed event (attempts reset). 204; 404 if not found / not yours / not failed |
Example — connect and check:
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, everTroubleshooting
Section titled “Troubleshooting”- Events stuck in
pending— the export is enabled? (enabled: falsepauses sending; the backlog waits and drains once re-enabled.) Checkoldest_pending_age_secondsinGET /gw/stripe/events. - Events
failed—last_errorcarries the (truncated) Stripe response. Typical causes: revoked/insufficient key (401/403— reconnect a key with meter-events write access), deleted customer, meter not configured forsociaro.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 byidentifier. - 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.