Source:
ocean/docs/adr/017-synchronous-user-provisioning.md| ✏️ Edit on GitHub
17. Synchronous User Provisioning During Signup
Date: 2025-08-13
Status
Accepted
Context
Our platform requires provisioning resources in external services (Stripe and Neon) when users sign up. The initial implementation used an asynchronous webhook-based approach that had several critical issues:
- Silent Failures: Provisioning failures were not visible to users, resulting in broken accounts
- Poor User Experience: Users could log in to incomplete accounts missing critical resources
- No Rollback: Failed provisioning left partial data across systems
- Debugging Difficulty: Async webhooks made it hard to trace provisioning failures
Decision
We will provision user resources synchronously during the signup flow, specifically during the window between sending the OTP email and the user entering their verification code.
Implementation Details
- Provisioning Trigger: Immediately after
signInWithOtpsucceeds (user created, email sent) — before OTP entry - Parallel Execution: Stripe customer and Neon database creation happen concurrently with idempotency guards
- GraphQL Mutation: Direct API call via
provisionUserResources(email, metadata)— server derivesuserIdby email using service role - Fire-and-Forget: Provisioning runs in background while user waits for email; resolver may short-poll for the organization record
- Region Selection: Users must select their data region during signup (permanent choice)
Architecture Flow
User Signup Form
↓
signInWithOtp (Creates user, sends email)
↓
provisionUserResources(email, metadata) (Runs immediately)
├─→ Create Stripe Customer
└─→ Create Neon Database
↓
User enters OTP code
↓
User logs in (resources ready)
Data Storage
- Supabase: User profiles, organizations, database metadata, billing information
- Neon: Actual tenant databases (region-specific)
- Stripe: Customer records and billing data
Region Mapping
We support 6 Neon regions mapped from user selection:
us-east-1→aws-us-east-1us-east-2→aws-us-east-2us-west-2→aws-us-west-2eu-central-1→aws-eu-central-1eu-west-2→aws-eu-west-2ap-southeast-1→aws-ap-southeast-1
Consequences
Positive
- Immediate Feedback: Provisioning errors can be logged and monitored
- Better UX: Resources are ready by the time user completes signup
- Simplified Architecture: No webhook complexity or retry logic needed
- Easier Debugging: Direct API calls with clear error paths
- Time Efficiency: Uses the natural waiting period (email delivery) productively
Negative
- No User Feedback on Failure: Since it's fire-and-forget, users won't see provisioning errors
- Potential Orphaned Accounts: User accounts may exist without properly provisioned resources
- No Retry Logic: Failed provisioning requires manual intervention
Mitigations
- Monitoring: Sentry alerts on provisioning failures for immediate response
- Admin Tools: Build admin interface to manually trigger re-provisioning
- Health Checks: Regular checks for accounts missing resources
Alternatives Considered
-
Synchronous Blocking: Wait for provisioning before showing OTP screen
- Rejected: Would add 3-5 seconds to signup flow
-
Webhook with Retry: Keep async but add retry logic
- Rejected: Complex, still has visibility issues
-
Two-Phase Signup: Create account first, provision on first login
- Rejected: Poor UX, users expect immediate access
Implementation Notes
- API Keys stored in Supabase Edge Function secrets
- Stripe requests include an
Idempotency-Keyper organization to avoid duplicates - Neon connection string stored in Supabase Secrets (Vault); no plaintext credentials in tables
- Resolver derives user by email (service role) and short-polls briefly for organization creation if needed
- Region mapping centralized; UI values are AWS-style (e.g.,
us-east-1) - Region selection is permanent (cannot be changed after creation)
- Clear UI warnings about permanent region choice
- Provisioning errors logged to Sentry for monitoring; metrics emitted for attempts/success/errors