Convex + WorkOS Real-Time Sync & Cloudflare Workers Plan
Created: December 25, 2025 Status: Completed Completed: December 25, 2025
Overview
This plan outlines the migration from our custom WorkOS → Convex sync implementation to the official @convex-dev/workos-authkit component, and the potential setup of Cloudflare Workers for do.dev.
Part 1: Official Convex + WorkOS AuthKit Component
What You Saw (The "Real-Time Link")
The videos you saw are likely about the official Convex × WorkOS integration released in September 2025. This is:
- Package:
@convex-dev/workos-authkit - Repo: github.com/get-convex/workos-authkit
- Docs: docs.convex.dev/auth/authkit
- Component Page: convex.dev/components/workos-authkit
Key Features
- Real-time user sync - WorkOS user changes automatically sync to Convex database via webhooks
- Convex Component architecture - Clean separation, maintains its own user table
- Event handlers - React to
user.created,user.updated,user.deletedevents - WorkOS Actions - Block registration/authentication based on custom logic
- Session events - Optional
session.created,session.revokedtracking
Current do-dev Implementation vs. Official Component
| Feature | Current (Custom) | Official Component |
|---|---|---|
| Webhook endpoint | /api/webhooks/workos (Next.js) | *.convex.site/workos/webhook (Convex HTTP) |
| User sync | Manual mutations (workosSync.ts) | Automatic via component |
| Event handling | Custom logic in API route | Typed event handlers |
| User table | Custom users table | Component's user table + optional custom |
| JWT validation | auth.config.ts (manual) | Handled by component |
| Actions (block auth) | Not implemented | Built-in support |
Part 2: Migration Plan
Phase 1: Install Official Component
# Install the package
pnpm add @convex-dev/workos-authkit
# Set webhook secret in Convex
npx convex env set WORKOS_WEBHOOK_SECRET=<your-webhook-secret>Phase 2: Configure Convex
2a. Update convex/convex.config.ts
// tools/convex/convex/convex.config.ts
import workOSAuthKit from "@convex-dev/workos-authkit/convex.config";
import { defineApp } from "convex/server";
const app = defineApp();
app.use(workOSAuthKit);
export default app;2b. Create AuthKit Client (convex/authkit.ts)
// tools/convex/convex/authkit.ts
import { AuthKit, type AuthFunctions } from "@convex-dev/workos-authkit";
import { components, internal } from "./_generated/api";
import type { DataModel } from "./_generated/dataModel";
const authFunctions: AuthFunctions = internal.authkit;
export const authKit = new AuthKit<DataModel>(components.workOSAuthKit, {
authFunctions,
});
// Export event handlers - this syncs to your custom users table
export const { authKitEvent } = authKit.events({
"user.created": async (ctx, event) => {
// Create user in your custom users table
const userId = `usr_${crypto.randomUUID().slice(0, 8)}`;
const custId = `cus_${crypto.randomUUID().slice(0, 8)}`;
await ctx.db.insert("users", {
email: event.data.email,
name: `${event.data.firstName ?? ""} ${event.data.lastName ?? ""}`.trim() || null,
image: event.data.profilePictureUrl ?? null,
roles: ["user"],
appId: "do-dev",
verified: event.data.emailVerified,
userId,
custId,
});
},
"user.updated": async (ctx, event) => {
const user = await ctx.db
.query("users")
.withIndex("by_app_email", (q) =>
q.eq("appId", "do-dev").eq("email", event.data.email)
)
.unique();
if (!user) {
console.warn(`User not found for update: ${event.data.email}`);
return;
}
await ctx.db.patch(user._id, {
name: `${event.data.firstName ?? ""} ${event.data.lastName ?? ""}`.trim() || null,
image: event.data.profilePictureUrl ?? null,
});
},
"user.deleted": async (ctx, event) => {
const user = await ctx.db
.query("users")
.withIndex("by_app_email", (q) =>
q.eq("appId", "do-dev").eq("email", event.data.email)
)
.unique();
if (user) {
await ctx.db.delete(user._id);
}
},
});2c. Register HTTP Routes (convex/http.ts)
// tools/convex/convex/http.ts
import { httpRouter } from "convex/server";
import { authKit } from "./authkit";
const http = httpRouter();
// Register WorkOS webhook routes
// Endpoint: https://<deployment>.convex.site/workos/webhook
authKit.registerRoutes(http);
export default http;Phase 3: Update WorkOS Dashboard
- Go to WorkOS Dashboard → Webhooks
- Create/update webhook with:
- Endpoint URL:
https://standing-bird-371.convex.site/workos/webhook - Events:
user.created,user.updated,user.deleted
- Endpoint URL:
- Copy webhook secret → Set in Convex env
Phase 4: Update auth.config.ts
// tools/convex/convex/auth.config.ts
const clientId = process.env.WORKOS_CLIENT_ID;
export default {
providers: [
{
type: "customJwt",
issuer: `https://api.workos.com/`,
algorithm: "RS256",
applicationID: clientId,
jwks: `https://api.workos.com/sso/jwks/${clientId}`,
},
{
type: "customJwt",
issuer: `https://api.workos.com/user_management/${clientId}`,
algorithm: "RS256",
jwks: `https://api.workos.com/sso/jwks/${clientId}`,
},
],
};Phase 5: Clean Up Old Code
After verifying the new setup works:
- Remove
/api/webhooks/workos/route.ts(Next.js webhook handler) - Remove
/api/users/sync/route.ts(manual sync endpoint) - Remove
/api/admin/sync-all-users/route.ts(bulk sync) - Remove
workosSync.ts(custom Convex mutations)
Part 3: Cloudflare Workers for do.dev
Why Cloudflare Workers?
- Edge performance - Run code globally, close to users
- Webhook handling - Alternative to Convex HTTP or Next.js API routes
- Background jobs - Scheduled tasks, cron jobs
- API gateway - Rate limiting, auth, routing
Option A: Deploy Entire Next.js to Cloudflare Workers
Using OpenNext Cloudflare Adapter (@opennextjs/cloudflare):
# Install adapter
pnpm add @opennextjs/cloudflare
# Configure wrangler.jsonc
{
"name": "do-dev",
"main": ".open-next/worker.js",
"compatibility_date": "2025-04-01",
"compatibility_flags": ["nodejs_compat"]
}Pros:
- Full Next.js app on edge
- Built-in KV, Durable Objects, D1 database access
Cons:
- Major infrastructure change
- Currently using Vercel (works well)
- Convex still runs separately
Option B: Cloudflare Worker for Webhooks Only
Create a separate Worker to handle webhooks and forward to Convex:
// workers/webhook-handler/src/index.ts
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/workos/webhook" && request.method === "POST") {
// Verify webhook signature
const signature = request.headers.get("workos-signature");
// ... verify with env.WORKOS_WEBHOOK_SECRET
// Forward to Convex
const convexResponse = await fetch(
`${env.CONVEX_SITE_URL}/workos/webhook`,
{
method: "POST",
headers: request.headers,
body: await request.text(),
}
);
return convexResponse;
}
return new Response("Not found", { status: 404 });
},
};Pros:
- Adds layer of control/logging
- Can add rate limiting, geo-blocking
- Keeps main app on Vercel
Cons:
- Extra hop (latency)
- More infrastructure to manage
- Not needed - Convex HTTP handles this natively
Option C: Cloudflare Worker for Background Jobs (Recommended Future Addition)
// workers/do-dev-jobs/src/index.ts
export default {
// Cron triggers
async scheduled(event: ScheduledEvent, env: Env) {
switch (event.cron) {
case "0 * * * *": // Hourly
await syncMetrics(env);
break;
case "0 0 * * *": // Daily
await generateReports(env);
break;
}
},
// HTTP triggers for on-demand jobs
async fetch(request: Request, env: Env) {
// Job queue processing, etc.
},
};Recommendations
Immediate (This Week)
- Migrate to
@convex-dev/workos-authkit- This is the official, supported way- Install package
- Configure Convex component
- Update WorkOS webhook endpoint
- Test thoroughly
- Remove old custom code
Short-Term (Next Month)
- Set up Cloudflare Workers project for future use:
packages/ └── cloudflare-workers/ ├── wrangler.toml └── src/ └── index.ts
When to Use Cloudflare Workers
- Background jobs/cron - Daily reports, cleanup tasks
- External API proxy - Rate limiting, caching
- Image processing - Resize, optimize
- NOT for webhooks - Convex HTTP handles this better
Migration Checklist
- Install
@convex-dev/workos-authkit - Create
convex/convex.config.tswith component - Create
convex/authkit.tswith event handlers - Create/update
convex/http.tswith routes - Update
convex/auth.config.tswith JWT providers - Set
WORKOS_WEBHOOK_SECRETin Convex env - Update WorkOS dashboard webhook endpoint
- Archive old custom sync code
- Deploy and test user creation flow
- Deploy and test user update flow
- Deploy and test user deletion flow
- Update documentation