Skip to content
Studeia Docs

Stripe integration (checkout + webhooks + billing portal)

Stripe is Studeia's default B2B international/card payment provider. Self-service checkout, idempotent webhooks for subscription lifecycle, Stripe self-manage portal, dual currency BRL + USD.

2026-05-24 6 min
Resposta curta

Stripe is Studeia's B2B international payment provider. Self-service checkout via Stripe Checkout Session, idempotent webhooks (HMAC SHA-256 + ordering guard via lastEventAt) for subscription lifecycle, Stripe self-manage portal for admin to change plan/card, dual currency BRL + USD detected by geolocation. For PIX/boleto Brazil: Asaas (parallel integration).

Setup

Stripe Dashboard

  1. Developers > API Keys > copy Secret Key
  2. Webhooks > Add endpoint > URL: https://[tenant].studeia.com/api/webhooks/stripe
  3. Events: checkout.session.completed, customer.subscription.{created,updated,deleted}, invoice.payment_{succeeded,failed}
  4. Copy webhook signing secret

Create products + prices

For each B2B plan (mini, growth, pro_100): Products > Add > Recurring monthly + price. Copy price_id.

Env vars

STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PRICE_MINI=price_...
STRIPE_PRICE_GROWTH=price_...
STRIPE_PRICE_PRO_100=price_...
STRIPE_PRICE_MINI_USD=price_...
STRIPE_PRICE_GROWTH_USD=price_...
STRIPE_PRICE_PRO_100_USD=price_...

Checkout flow

Admin clicks "Subscribe Mini" → POST /api/institution/billing/checkout → Studeia createCheckoutSession() (resolveCustomerIdForProvider, getCurrencyFromHeaders, create Stripe Session) → returns Stripe URL → admin pays → webhook checkout.session.completed → Studeia validates (HMAC + retrieve subscription + cross-check tenantId + price_id allowlist) → promotes Tenant.plan + creates PaymentLog (idempotent).

Hardening (rules 129-139)

  • Webhook returns 5xx on failure (Stripe retries 3 days)
  • Ordering guard: lastEventAt/lastEventId in TenantSubscription
  • Idempotency: unique [provider, externalEventId] in PaymentLog
  • Zod schema: z.enum(PAID_PLAN_SLUGS) in checkout body
  • PII redact: PaymentLog.rawPayload via redactPaymentPayload()
  • Past_due gate: isAccessBlocked(status) in layouts. Cron /api/cron/billing-grace-expire transitions after grace 7d.

Stripe self-manage portal

POST /api/institution/billing/portal returns temporary Stripe portal URL: admin changes plan, updates card, sees invoices, cancels.

Multi-currency

AspectBRLUSD
DetectionDefaultx-vercel-ip-country / cf-ipcountry
Price IDsSTRIPE_PRICE_*STRIPE_PRICE_*_USD
Asaas fallbackYes (PIX/boleto)NO (Brazil only)

See also

FAQ

Is Stripe how Studeia receives subscription payments?

Yes, for B2B international/card. Plan selected in /institution/billing > Studeia calls POST /api/institution/billing/checkout which creates Stripe Checkout Session > admin pays > subscription.activated webhook auto-promotes tenant.plan. For PIX/boleto Brazil: use Asaas (parallel integration).

Are Stripe webhooks reliable?

Yes, but Studeia treats defensively. (1) Verifies HMAC SHA-256 via stripe-signature header with STRIPE_WEBHOOK_SECRET. (2) Stripe retries 3 days with backoff on 5xx. (3) Idempotency via unique [provider, externalEventId] in PaymentLog. (4) Ordering guard via lastEventAt/lastEventId. (5) checkout.session.completed always does stripe.subscriptions.retrieve() to validate (doesn't trust metadata).

Does Studeia support USD for clients outside Brazil?

Yes. STRIPE_PRICE_IDS (BRL) + STRIPE_PRICE_IDS_USD (USD) configurable via env vars. Detects currency server-side via x-vercel-ip-country / cf-ipcountry header. Client outside BR sees USD price automatically.

Can I use Stripe Customer Portal for self-service?

Yes. POST /api/institution/billing/portal returns Stripe portal URL — admin changes plan, updates card, sees invoices, cancels. Without going through support.

Veja tambem

Stripe integration (checkout + webhooks + billing portal)