Database Webhooks in the Next.js Supabase Starter Kit
Handle database change events with webhooks to send notifications, sync external services, and trigger custom logic when data changes.
Database webhooks let you execute custom code when rows are inserted, updated, or deleted in your Supabase tables. Makerkit provides a typed webhook handler at @kit/database-webhooks that processes these events in a Next.js API route.
Database Webhooks Setup
Configure and handle database change events
How Database Webhooks Work
Supabase database webhooks fire HTTP requests to your application when specified database events occur. The flow is:
- A row is inserted, updated, or deleted in a table
- Supabase sends a POST request to your webhook endpoint
- Your handler processes the event and executes custom logic
- The handler returns a success response
Makerkit includes built-in handlers for:
- User deletion: Cleans up related subscriptions and data
- User signup: Sends welcome emails
- Invitation creation: Sends invitation emails
You can extend this with your own handlers.
Adding Custom Webhook Handlers
The webhook endpoint is at apps/web/app/api/db/webhook/route.ts. Add your handlers to the handleEvent callback:
apps/web/app/api/db/webhook/route.ts
import { getDatabaseWebhookHandlerService } from '@kit/database-webhooks';import { enhanceRouteHandler } from '@kit/next/routes';export const POST = enhanceRouteHandler( async ({ request }) => { const service = getDatabaseWebhookHandlerService(); try { const signature = request.headers.get('X-Supabase-Event-Signature'); if (!signature) { return new Response('Missing signature', { status: 400 }); } const body = await request.clone().json(); await service.handleWebhook({ body, signature, async handleEvent(change) { // Handle new project creation if (change.type === 'INSERT' && change.table === 'projects') { await notifyTeamOfNewProject(change.record); } // Handle subscription cancellation if (change.type === 'UPDATE' && change.table === 'subscriptions') { if (change.record.status === 'canceled') { await sendCancellationSurvey(change.record); } } // Handle user deletion if (change.type === 'DELETE' && change.table === 'accounts') { await cleanupExternalServices(change.old_record); } }, }); return new Response(null, { status: 200 }); } catch (error) { console.error('Webhook error:', error); return new Response(null, { status: 500 }); } }, { auth: false },);RecordChange Type
The change object is typed to your database schema:
import type { Database } from '@kit/supabase/database';type Tables = Database['public']['Tables'];type TableChangeType = 'INSERT' | 'UPDATE' | 'DELETE';interface RecordChange< Table extends keyof Tables, Row = Tables[Table]['Row'],> { type: TableChangeType; table: Table; record: Row; // Current row data (null for DELETE) schema: 'public'; old_record: Row | null; // Previous row data (null for INSERT)}Type-Safe Handlers
Cast to specific table types for better type safety:
import type { RecordChange } from '@kit/database-webhooks';type ProjectChange = RecordChange<'projects'>;type SubscriptionChange = RecordChange<'subscriptions'>;async function handleEvent(change: RecordChange<keyof Tables>) { if (change.table === 'projects') { const projectChange = change as ProjectChange; // projectChange.record is now typed to the projects table console.log(projectChange.record.name); }}Async Handlers
For long-running operations, consider using background jobs:
async handleEvent(change) { if (change.type === 'INSERT' && change.table === 'orders') { // Queue for background processing instead of blocking await queueOrderProcessing(change.record.id); }}Configuring Webhook Triggers
Webhooks are configured in Supabase. You can set them up via SQL or the Dashboard.
SQL Configuration
Add a trigger in your schema file at apps/web/supabase/schemas/:
apps/web/supabase/schemas/webhooks.sql
-- Create the webhook trigger for the projects tablecreate trigger projects_webhook after insert or update or delete on public.projects for each row execute function supabase_functions.http_request( 'https://your-app.com/api/db/webhook', 'POST', '{"Content-Type":"application/json"}', '{}', '5000' );Dashboard Configuration
- Open your Supabase project dashboard
- Navigate to Database > Webhooks
- Click Create a new hook
- Configure:
- Name:
projects_webhook - Table:
projects - Events: INSERT, UPDATE, DELETE
- Type: HTTP Request
- URL:
https://your-app.com/api/db/webhook - Method: POST
- Name:
Webhook Security
Supabase automatically signs webhook payloads using the X-Supabase-Event-Signature header. The @kit/database-webhooks package verifies this signature against your SUPABASE_DB_WEBHOOK_SECRET environment variable.
Configure the webhook secret:
.env.local
SUPABASE_DB_WEBHOOK_SECRET=your-webhook-secretSet the same secret in your Supabase webhook configuration. The handler validates signatures automatically, rejecting requests with missing or invalid signatures.
Testing Webhooks Locally
Local Development Setup
When running Supabase locally, webhooks need to reach your Next.js server:
- Start your development server on a known port:pnpm run dev
- Configure the webhook URL in your local Supabase to point to
http://host.docker.internal:3000/api/db/webhook(Docker) orhttp://localhost:3000/api/db/webhook.
Manual Testing
Test your webhook handler by sending a mock request:
curl -X POST http://localhost:3000/api/db/webhook \ -H "Content-Type: application/json" \ -H "x-webhook-secret: your-secret-key" \ -d '{ "type": "INSERT", "table": "projects", "schema": "public", "record": { "id": "test-id", "name": "Test Project", "account_id": "account-id" }, "old_record": null }'Expected response: 200 OK
Debugging Tips
Webhook not firing: Check that the trigger exists in Supabase and the URL is correct.
Handler not executing: Add logging to trace the event flow:
async handleEvent(change) { console.log('Received webhook:', { type: change.type, table: change.table, recordId: change.record?.id, });}Timeout errors: Move long operations to background jobs. Webhooks should respond quickly.
Common Use Cases
| Use Case | Trigger | Action |
|---|---|---|
| Welcome email | INSERT on users | Send onboarding email |
| Invitation email | INSERT on invitations | Send invite link |
| Subscription change | UPDATE on subscriptions | Sync with CRM |
| User deletion | DELETE on accounts | Clean up external services |
| Audit logging | INSERT/UPDATE/DELETE | Write to audit table |
| Search indexing | INSERT/UPDATE | Update search index |
Related Resources
- Database Schema for extending your schema
- Database Functions for built-in SQL functions
- Email Configuration for sending emails from webhooks