For the complete documentation index, see llms.txt. Prefer markdown by appending .md to documentation URLs or sending Accept: text/markdown.

Integrations

Verify webhook signatures, keep storage buckets private with short-lived signed URLs, and never ship third-party API keys to the client.

Most breaches in SaaS kits do not come from exotic crypto - they come from trusting an unverified webhook, a public bucket, or a leaked provider key. TurboStarter wires the common integrations to fail closed.

Billing webhooks

Every supported web billing provider verifies the request signature before applying subscription changes.

ProviderSecret variableVerification
StripeSTRIPE_WEBHOOK_SECRETstripe.webhooks.constructEvent
Lemon SqueezyLEMON_SQUEEZY_SIGNING_SECRETHMAC + timing-safe compare
PolarPOLAR_WEBHOOK_SECRETSDK unwrap / signature check
Dodo PaymentsDODO_PAYMENTS_WEBHOOK_KEYSDK webhooks.unwrap

The API mounts the active provider webhook without authentication middleware on purpose - the signature is the credential:

packages/api/src/modules/billing/router.ts
.post(`/webhook/${provider}`, (c) => webhookHandler(c.req.raw))

Rules when you extend webhooks:

  1. Read the raw body for signature checks (do not re-serialize JSON first)
  2. Reject missing or invalid signatures with an error - never “best effort” sync
  3. Use environment-specific secrets (test vs live)
  4. Keep custom side effects inside the verified handler callbacks
webhookHandler(c.req.raw, {
  onSubscriptionUpdated: async (subscriptionId) => {
    // Safe: only runs after signature verification
  },
});

Background task signatures

If you use QStash (or similar), verify the Upstash signature on every task route the same way you verify billing webhooks. An open cron endpoint is a free remote code trigger.

File storage

Storage credentials stay in @workspace/storage/server. The browser never sees AWS keys - it receives short-lived presigned URLs from authenticated API routes.

EndpointAuthPurpose
GET /storage/uploadenforceAuthPresigned PUT (expires in 60s)
GET /storage/signedenforceAuthPresigned GET (expires in 1h)
GET /storage/deleteenforceAuthPresigned DELETE (expires in 60s)
GET /storage/publicpublicPublic object URL helper

Recommendations:

  • Keep buckets private by default
  • Prefer path prefixes scoped to userId / organizationId
  • Treat a presigned URL like a temporary password - anyone with the URL can use it until it expires
  • Configure CORS narrowly for your app origin
  • Enable provider-side encryption when storing sensitive files

Managing files

Upload flows, permissions, and signed URL usage.

Third-party API keys

OpenAI, Resend, Stripe, OAuth client secrets, analytics write keys that grant privileged access - all of these belong in server env presets.

Patterns that stay safe:

  • Call providers from Hono routers, Server Components, or background workers
  • Return only the UI-facing result to the client
  • For browser uploads to third parties, use short-lived tokens or signed URLs issued by your API - not the master key

Patterns that leak:

  • NEXT_PUBLIC_ on a secret key
  • Passing process.env.OPENAI_API_KEY into a Client Component
  • Logging full webhook payloads or Authorization headers in production

Email and OAuth

  • OAuth client secrets are server-only; only client ids that providers document as public may appear in the browser
  • Configure production callback URLs carefully - open redirects in auth flows are a common footgun
  • Keep transactional email templates free of secrets; confirmation links should expire and be single-use (Better Auth handles this for built-in flows)

Monitoring without leaking data

Error trackers (for example Sentry) are valuable, but scrub:

  • Cookies and Authorization headers
  • Raw billing webhook bodies
  • Password, OTP, and recovery code fields

Ship enough context to debug; never enough to impersonate a user.

How is this guide?

Last updated on

On this page

Ship your startup everywhere. In minutes.Try TurboStarter