🎣 Supabase Webhooks: Automating Your Database
In traditional backend development, triggering actions when "data changes" is often cumbersome.
For example: "When a user registers successfully, send them a welcome email."
The traditional approach is:
- The frontend calls
/api/register. - The backend writes in the API:
db.users.insert(...). - If the insert succeeds, the backend then calls the email-sending code
sendWelcomeEmail(...). - If the email fails, retry logic must be handled.
The problem with this approach: If one day we create another API /api/admin/create-user, or the boss manually adds a user directly in the database backend, those users won’t receive a welcome email because the logic is hardcoded in a specific API.
Is there a way to say, "No matter who modifies this table, as long as new data is added, the database automatically triggers the email"?
Yes! This is Database Webhooks (Database Triggers)!
1. What Are Database Webhooks?
At the core of PostgreSQL, there’s a powerful feature called Triggers. It can listen for INSERT, UPDATE, and DELETE events on a specific table.
The Supabase team has wrapped this underlying functionality into an easy-to-use Database Webhooks.
Here’s how it works:
When [this table] undergoes [insert/update/delete], automatically send an [HTTP POST request] to [your specified URL].
This liberates events previously confined within the database to the entire internet!
2. Practical Scenario: Order Status Update Notifications
Suppose we have an orders table with a status column (possible values: pending, paid, shipped).
We want: "Whenever an order’s status is updated to paid, call an API to notify the boss to ship (or integrate with Line Notify)."
Step 1: Write the Webhook-Receiving API (Your Next.js)
First, in our Next.js project, prepare an API to receive the webhook:
// src/app/api/webhooks/order-paid/route.ts
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
try {
// 1. Get the Webhook Payload from Supabase
const body = await request.json();
// The data format from Supabase Webhook looks roughly like this:
// {
// "type": "UPDATE",
// "table": "orders",
// "record": { "id": "123", "status": "paid", "amount": 500 }, // New data
// "old_record": { "id": "123", "status": "pending", "amount": 500 } // Old data
// }
const newRecord = body.record;
const oldRecord = body.old_record;
// 2. Logic check: Ensure it’s truly a transition to 'paid'
if (newRecord.status === 'paid' && oldRecord.status !== 'paid') {
console.log(`🎉 Payment received! Order ID: ${newRecord.id}, Amount: ${newRecord.amount}`);
// TODO: Here, you can call an email service (Resend) or Line Notify!
// sendLineNotify(`Order ${newRecord.id} has been paid, ship it now!`);
}
return NextResponse.json({ success: true });
} catch (error) {
return NextResponse.json({ error: 'Webhook processing failed' }, { status: 500 });
}
}
Step 2: Configure the Webhook in Supabase Dashboard
Now, we’ll tell Supabase to push events to this API.
- Log in to the Supabase dashboard.
- In the left menu, select Database -> Webhooks.
- Click Create a new Hook in the top-right corner.
- Set the conditions:
- Name:
Order Paid Webhook - Table: Select the
orderstable - Events: Check
Update(since we’re listening for status updates)
- Name:
- Configure the HTTP request:
- Method:
POST - URL: Enter your API URL (if developing locally, use Ngrok to expose localhost to the public internet, e.g.,
https://your-ngrok.app/api/webhooks/order-paid)
- Method:
- Set HTTP Headers:
- Content-type:
application/json - (Strongly recommended) Add a Secret Token:
Authorization: Bearer my-super-secret-token
- Content-type:
- Click Save hook.
3. Security: How to Verify Webhook Authenticity?
This is a serious concern: Your /api/webhooks/order-paid is publicly accessible on the internet!
If a hacker guesses this URL, they could send a fake POST request with {"status": "paid"} order data, and your system would think orders are being paid and ship endlessly!
Defense Method: Verify the Secret Token
In the Supabase Webhook setup (Step 6), we secretly added a custom HTTP Header:
Authorization: Bearer my-super-secret-token
Now, back in our Next.js code, add a guard:
// src/app/api/webhooks/order-paid/route.ts
export async function POST(request: Request) {
// 🛡️ First line of defense: Check the secret key
const authHeader = request.headers.get('Authorization');
if (authHeader !== `Bearer ${process.env.SUPABASE_WEBHOOK_SECRET}`) {
console.error("Hacker attack! Webhook verification failed!");
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// Key is correct, proceed with the shipping logic...
const body = await request.json();
// ...
}
This way, only the Supabase server with knowledge of SUPABASE_WEBHOOK_SECRET can successfully knock on your door!
4. Edge Functions vs. Webhooks
You might ask: Supabase also offers Serverless Functions (Edge Functions). How is this different from Webhooks?
- Edge Functions: Code hosted on Supabase (Deno environment). Suitable for lightweight computational logic.
- Database Webhooks: Push events externally. Typically recommended to push events to your own Next.js project (Node.js environment).
Practical Advice:
If you’re a full-stack developer using Next.js App Router, strongly prefer using Webhooks to push back to your Next.js API.
Your Next.js already has a complete ORM (Prisma/Drizzle), email-sending modules, and payment modules. Instead of rewriting this logic in Supabase Edge Functions, centralize all business logic in Next.js for a much smoother development experience!
Mastering Database Webhooks upgrades your architecture from "passive queries" to "event-driven." This is the ultimate secret to building large-scale, extensible systems!