Request lifecycle
Page request
-
middleware.tsruns for every non-excluded path. It short-circuits/.well-known,/icons/and/assets/, then reads the Supabase session from cookies. -
If a session exists, the user’s email is checked against
isAllowedPayabilityEmail()— anything outsidepayability.com(or a subdomain) is signed out and redirected to/login?error=forbidden_domain. -
Unauthenticated requests to anything other than
/login,/signup,/auth/callback,/docsor/apiare redirected to/login?redirectedFrom=<path>. -
Authenticated requests to
/loginor/signupare redirected to/. -
The dashboard layout renders inside WorkspaceGate, which blocks rendering until
GET /api/meresolves and the user has at least one workspace with an active selection.
Excluded from middleware entirely (see the matcher in middleware.ts): _next/static,
_next/image, favicon.ico, icons, assets, images, fonts, supabase/functions,
api/webhooks, .well-known.
API request
sequenceDiagram
participant C as Client (React Query hook)
participant R as Route handler
participant A as resolveSubscription()
participant S as Service
participant D as Repository
participant P as PostgreSQL
C->>R: fetch("/api/…", { cache: "no-store" })
R->>A: resolve user + active workspace
A->>P: Profile + SubscriptionUser lookup
A-->>R: { userId, subscriptionId }
R->>R: Zod parse body / query
R->>S: delegate
S->>D: repository call
D->>P: Prisma query (scoped by subscriptionId)
P-->>D: rows
D-->>S: models
S-->>R: domain result
R-->>C: snake_case JSON
C->>C: Schema.parse(response)
resolveSubscription() also stamps Sentry with the user id, email and subscriptionId tag,
so any error captured later in the request is already attributed to a tenant.
Webhook request
Provider webhooks bypass middleware (api/webhooks is excluded from the matcher) and
authenticate themselves:
-
Read the raw body as text — signature verification needs the exact bytes.
-
Verify the provider signature where one exists (Svix for Resend, HMAC for HubSpot, optional bearer/
X-Nooks-Signaturefor Nooks). -
Upsert a
WebhookEventrow, unique on(source, eventId). A duplicate delivery is a no-op. -
Dispatch to the provider’s registry, which instantiates the handler for that event type.
-
Return
200with{ received: true }even if the handler failed — the raw event is stored and can be replayed, and a500would only trigger provider retries of work that may have partially succeeded.
See Events for the dispatchers and handlers.
Machine request
pg_cron, Supabase Edge Functions and other internal services call broadcast and cron
endpoints with Authorization: Bearer <INTERNAL_API_SECRET>, verified by
verifyInternalSecret() in lib/auth/internal-secret.ts. It fails closed: if the secret is
unset, every request is rejected.
The MCP endpoint uses its own shared secret (MCP_API_SECRET) plus an explicit
x-subscription-id header instead of a cookie — see MCP tool server.