ee/api/vercel/README.md
This guide documents the complete setup process for testing Vercel SSO integration locally using ngrok for HTTPS.
brew install --cask ngrokbrew install --cask 1password-cliFollow the PostHog ngrok setup guide to configure ngrok.
Quick version:
ngrok config check --log=stdoutngrok start djangoThis will give you an HTTPS URL for port 8000, e.g., https://abc123.ngrok-free.dev
Note: You only need the Django tunnel. Vite will serve assets locally on port 8234, and the Django tunnel will proxy requests to it.
Create a .env.vercel file in the PostHog root directory with your ngrok URL (copy from .env.vercel.example in this directory)
# Enable HTTPS mode for local development
LOCAL_HTTPS=1
DEBUG=1
# ngrok tunnel URLs for HTTPS local development (update with your ngrok URL)
SITE_URL=https://your-ngrok-tunnel.ngrok-free.dev
JS_URL=https://your-ngrok-tunnel.ngrok-free.dev
WEBPACK_HOT_RELOAD_HOST=0.0.0.0
# Vercel integration credentials (automatically loaded from 1Password)
VERCEL_CLIENT_INTEGRATION_ID="op://General/Vercel Client Integration Secret/client id"
VERCEL_CLIENT_INTEGRATION_SECRET="op://General/Vercel Client Integration Secret/client secret"
Notes:
frontend/vite.config.ts to add your ngrok domain to the cors.origin array and allowedHosts. Do not commit these changes - they are for local development only.In your Vercel integration settings:
Configuration URL: Set to your Django tunnel URL (port 8000)
https://abc123.ngrok-free.devRedirect URL: Set to {your_ngrok_url}/connect/vercel/callback
https://abc123.ngrok-free.dev/connect/vercel/callbackRun PostHog with 1Password to load the Vercel credentials:
op run --env-file=.env.vercel -- bin/start
This will:
.env.vercel fileVercel marketplace integrations have two separate flows:
Installation Flow (API-based):
/api/vercel/v1/installations/{id}SSO Flow (web-based):
/login/vercel/url parameterWhen a user clicks "Link Existing Account" in the Vercel Marketplace:
/connect/vercel/callback) with an OAuth codePOST /v2/oauth/access_token/login firstOrganizationIntegration record is created with type=connectableBilling stays with PostHog for connected accounts - no billing provider migration is needed.
Solution: Add ngrok domains to allowedHosts in vite.config.ts (see Step 3)
Solution: Add your ngrok domain to the cors.origin array in vite.config.ts (see Step 3)
Solution: Verify VERCEL_CLIENT_INTEGRATION_ID matches your Vercel integration's Client ID exactly
Solution:
{ngrok_url}/connect/vercel/callbackVite caches resolved module paths in node_modules/.pnpm.
When you switch branches, pnpm store hashes can change, leaving Vite with stale references (typically @react-refresh returns 500).
Solution: Restart the Vite dev server.
You can verify by checking curl -s -o /dev/null -w "%{http_code}" http://localhost:8234/@react-refresh — it should return 200.
Solution:
op whoamiop run --env-file=.env.vercel -- printenv | grep VERCELJS_URL and NGROK_ORIGIN in your .env.vercel match your ngrok tunnel URLJS_URL doesn't affect Vite dev scriptsIn development, the Vite dev server script tags (@vite/client, @react-refresh, src/index.tsx) are hardcoded to http://localhost:8234 in posthog/utils.py.
JS_URL only sets window.JS_URL for the production bundle loader.
Browsers exempt localhost from mixed content blocking, so this works even when the page is served over HTTPS via ngrok.
This is normal! 1Password CLI conceals sensitive values in stdout for security. The actual values are correctly passed to your application as environment variables.
Run the Vercel integration test suite:
pytest ee/vercel/test/test_integration.py -v
The test suite includes:
TestVercelInstallationRegressions)TestVercelInstallationE2E)Before releasing changes to the Vercel integration, manually verify the following scenarios:
Setup: Use an email that doesn't exist in PostHog
Expected:
Setup: Create a PostHog account manually with an email, then use that email in Vercel
User.objects.create_user(email="[email protected]", password="test")Expected:
Setup: User who already has one Vercel installation
Expected:
Setup: Create an inactive user, then install with that email
User.objects.create_user(email="[email protected]", password="test", is_active=False)Expected:
Setup: Have an existing PostHog account with admin access to an organization
Expected:
OrganizationIntegration record created with config.type = "connectable"To quickly verify the fix for existing users without Vercel mappings:
# In Django shell: python manage.py shell
from posthog.models.user import User
from posthog.models.organization_integration import OrganizationIntegration
# Create test user without any Vercel mappings
User.objects.create_user(email="[email protected]", password="test", first_name="Test")
# Verify no Vercel mappings exist
for oi in OrganizationIntegration.objects.filter(kind="vercel"):
mappings = oi.config.get("user_mappings", {})
user_ids = list(mappings.values())
user = User.objects.filter(email="[email protected]").first()
assert user.pk not in user_ids, "User should have no mappings"
# Now install via Vercel with [email protected]
# User should be added to org without errors
When you're done testing:
# Stop ngrok
# Press Ctrl+C in the ngrok terminal
# Stop Django and Vite servers
# Press Ctrl+C in each terminal
# (Optional) Unset environment variables
direnv deny