Skip to main content

Source: ocean/docs/adr/0013-consolidate-cloudflare-worker-to-supabase.md | ✏️ Edit on GitHub

ADR-0013: Consolidate Cloudflare Worker to Supabase Edge Functions

Status

Accepted

Context

The application had a Cloudflare Worker deployed separately to handle user creation webhooks from Supabase. When a new user signed up, the worker would:

  1. Receive the webhook from Supabase
  2. Create a Stripe customer
  3. Update the organization with the Stripe customer ID

This created unnecessary complexity:

  • Multiple platforms: Frontend on Vercel, backend on Supabase, webhook handler on Cloudflare
  • Separate deployment pipeline: Additional GitHub Actions workflow for Cloudflare
  • Configuration overhead: Separate secrets management in Cloudflare
  • Operational complexity: Another service to monitor and maintain

Meanwhile, we already had a similar Supabase Edge Function (sync-user-to-stripe) that performed almost the same functionality.

Decision

We will consolidate all webhook handling into Supabase Edge Functions and remove the Cloudflare Worker entirely.

Implementation

1. Enhanced Supabase Edge Function

Updated /supabase/functions/sync-user-to-stripe/index.ts to:

  • Find the user's organization via organization_members table
  • Create the Stripe customer with organization metadata
  • Update the organization with the Stripe customer ID
  • Handle edge cases where organization doesn't exist yet

2. Database Trigger

Created a database trigger that automatically calls the Edge Function when a user is created:

CREATE TRIGGER sync_user_to_stripe_on_insert
AFTER INSERT ON auth.users
FOR EACH ROW
EXECUTE FUNCTION public.sync_new_user_to_stripe();

The trigger function uses pg_net to make an HTTP request to the Edge Function, keeping everything within Supabase's infrastructure.

3. Removed Cloudflare Infrastructure

  • Deleted /cloudflare/worker/ directory
  • Removed .github/workflows/deploy-cloudflare-worker.yml
  • No longer need Cloudflare API tokens or deployment configuration

Architecture Before

┌─────────────┐     ┌─────────────┐     ┌──────────────┐
│ Vercel │ │ Supabase │────▶│ Cloudflare │
│ (Frontend) │ │ (Database) │ │ Worker │
└─────────────┘ └─────────────┘ └──────────────┘
│ │
│ ▼
│ ┌─────────────┐
└─────────────▶│ Stripe │
└─────────────┘

Architecture After

┌─────────────┐     ┌─────────────────────────────────┐
│ Vercel │ │ Supabase │
│ (Frontend) │────▶│ Database + Edge Functions │
└─────────────┘ │ + GraphQL + Auth │
└─────────────────────────────────┘


┌─────────────┐
│ Stripe │
└─────────────┘

Consequences

Positive

  1. Simplified stack: Only two platforms (Vercel + Supabase) instead of three
  2. Unified backend: All backend logic in one place
  3. Simplified deployment: One less CI/CD pipeline to maintain
  4. Consistent security: All backend services use Supabase's security model
  5. Easier debugging: All logs and monitoring in Supabase dashboard
  6. Cost efficiency: No additional Cloudflare Workers charges
  7. Single source of truth: All configuration in Supabase environment

Negative

  1. Vendor lock-in: More dependent on Supabase's ecosystem
  2. Migration effort: Need to update any documentation or runbooks
  3. Trigger complexity: Database triggers are harder to debug than webhooks

Neutral

  1. Performance: Both solutions have similar latency characteristics
  2. Reliability: Both Cloudflare and Supabase have excellent uptime
  3. Scalability: Both can handle high webhook volumes

Migration Notes

  1. The database trigger is created in migration 20250729_create_user_sync_webhook.sql
  2. Existing users already have Stripe customers, so this only affects new signups
  3. The retry_user_stripe_sync function allows manual retries if needed
  4. Monitor net.http_request_queue table for webhook delivery status

Security Considerations

  1. Authentication: Uses Supabase service role key from vault
  2. Network: All communication stays within Supabase's network
  3. Logging: Webhook requests are logged for audit trail
  4. Error handling: Failed webhooks can be retried manually

Alternatives Considered

1. Keep Cloudflare Worker

  • Rejected: Adds unnecessary complexity for minimal benefit

2. Direct Database-to-Stripe Integration

  • Rejected: Would require storing Stripe keys in database

3. Queue-based System

  • Rejected: Over-engineering for simple webhook needs

References

  • ADR-0001: Adopt TanStack Ecosystem
  • ADR-0012: GraphQL-Based Billing Architecture