How to trigger a webhook on a new Supabase row insert
Last updated: August 3, 2026
From the SoleOS answers series — written about our own product space; grounded in published definitions and documented behavior, never invented numbers.
Supabase can call any HTTP endpoint the moment a row is inserted (or updated, or deleted) using Database Webhooks, which are built on Postgres triggers under the hood. You don't need a cron job, a polling script, or a separate queue — you point Supabase at a URL, pick the table and event, and it sends a POST request with the new row's data as JSON. The whole setup takes about five minutes in the dashboard, or a few lines of SQL if you prefer to script it.
Below is the exact path, what the payload looks like, and the failure modes that trip people up — especially the one where the webhook "stops working" silently and nobody notices for weeks.
Set it up from the dashboard
- In your Supabase project, go to Database → Webhooks.
- Click Create a new hook.
- Pick the table and the events you care about — for a new-row trigger, check only
Insert(leaveUpdateandDeleteunchecked unless you actually need them, since every unchecked event is one less place for a bug to hide). - Set the type to
HTTP Request, choosePOST, and paste your endpoint URL. - Add any headers you need — most people add a shared secret here (e.g.
X-Webhook-Secret: yourvalue) so the receiving endpoint can verify the call actually came from your project. - Save. Supabase creates a Postgres trigger and a matching
pg_netHTTP call behind the scenes.
That's it — inserts on that table now fire the webhook. Test it by inserting a row manually in the Table Editor and checking your endpoint's logs.
Set it up with SQL, if you'd rather version it
If you manage your schema with migrations, doing it via SQL keeps the trigger in source control instead of only living in the dashboard:
select
net.http_post(
url := 'https://your-endpoint.example.com/webhook',
headers := jsonb_build_object('Content-Type', 'application/json', 'X-Webhook-Secret', 'yourvalue'),
body := jsonb_build_object('record', row_to_json(new))
)
from new;
This gets wrapped in a trigger function bound to AFTER INSERT on your table. The Supabase dashboard generates equivalent SQL for you if you want a starting point — create the hook in the UI once, then copy the generated trigger definition into a migration file.
What the payload actually contains
A Database Webhook POST body looks like this:
{
"type": "INSERT",
"table": "signups",
"schema": "public",
"record": { "id": 42, "email": "user@example.com", "created_at": "2024-01-15T10:00:00Z" },
"old_record": null
}
record is the full new row as inserted. There's no diffing or partial payload — if you only need three columns, filter them on the receiving end, or use a Postgres VIEW-backed trigger if you want to shape the payload before it leaves the database.
Things that will bite you later
Timeouts are short. The underlying pg_net call has a timeout in the low seconds. If your receiving endpoint does anything slow — a synchronous call to a third-party API, a slow cold start on a serverless function — the webhook will time out and Supabase will not retry indefinitely. Design the receiver to accept the payload fast (return 200 immediately) and do the slow work asynchronously afterward.
Silent failures don't page anyone. If your endpoint starts returning 500s — expired auth token, redeployed with a broken route, hit a rate limit — Supabase logs the failure in net._http_response, but nothing alerts you by default. The practical fix is to check that table periodically, or point the webhook at a service that surfaces delivery failures (many webhook-relay tools do this out of the box).
RLS doesn't apply the way you'd expect. The trigger runs with the privileges of the function owner, not the inserting user's role, so row-level security policies on SELECT won't hide anything from the payload. If a row has sensitive columns you don't want leaving your database, exclude them explicitly in the trigger body rather than assuming RLS protects the webhook payload.
Batched inserts fire one webhook per row. If you bulk-insert 500 rows in one statement, that's 500 outbound HTTP calls, not one. If you're piping signups into an analytics tool or a Zapier-style automation, this can burn through rate limits or usage-based pricing fast. For bulk operations, consider a separate batched sync path instead of relying on the per-row webhook.
When you'd use this instead of an API poll
Webhooks make sense when you want near-real-time reaction to specific events — send a Slack message on signup, kick off an onboarding email, sync a new customer into another system. They're the wrong tool if you just want periodic aggregate numbers (daily signup counts, weekly active users) — for that, a scheduled query or a metrics dashboard that pulls on its own schedule is simpler and has fewer moving parts to babysit. Also worth checking what pulls Firebase signals outside its own console if you're running the same signup-tracking problem on a Firebase-backed app instead of Supabase.
Where this fits if you run multiple products
If you've got signup or activation events firing from Supabase on one or two apps, a single webhook endpoint is easy to manage. It gets messier once you're running Supabase on some apps, Firebase on others, and you're trying to answer "how many people signed up across my portfolio this week" without opening five dashboards.
Disclosure: SoleOS is portfolio intelligence for multi-product founders, and this post is written by the SoleOS team about our own connector space — so take the framing with that in mind. SoleOS connects to Supabase (OAuth or webhook) as a signals source alongside metrics connectors like Stripe, RevenueCat, and GA4, so a signup event can show up next to your revenue numbers instead of in a separate inbox. You can see exactly what scopes it requests on the connectors page, or poke at the live demo with sample data before connecting anything real.
If you're only running one product, or you just need a Slack ping when someone signs up, a raw Supabase webhook pointed at a Slack incoming-webhook URL is genuinely enough — you don't need a portfolio tool for that. It's the "aggregate across many apps" step where a dashboard starts pulling its weight instead of a single webhook.
Frequently asked questions
Does Supabase retry failed webhook deliveries?
Supabase's Database Webhooks don't have a robust built-in retry-with-backoff system the way some dedicated webhook infrastructure does. A failed call (timeout, 4xx, 5xx) is logged, but you shouldn't assume it will be automatically retried later. If delivery reliability matters, put a queue or a webhook-relay service in front of your real endpoint so failures get retried on your terms.
Can I trigger a webhook only when specific columns change?
For UPDATE events, Database Webhooks fire on any column change to the row by default — there's no column-level filter in the dashboard UI. You can get column-level control by writing a custom trigger function in SQL that compares old_record fields to new_record fields and only calls net.http_post when the columns you care about actually changed.
Is the webhook payload signed, so I can verify it's really from Supabase?
Not with a signature the way Stripe signs its webhook payloads. The common workaround is adding a custom header with a shared secret (as shown in the setup steps above) and checking for that exact value on the receiving end. It's not cryptographic verification, but it stops random internet traffic from hitting your endpoint successfully.
What happens if my endpoint is down when a row is inserted?
The webhook call fails and is logged as an error in Supabase's internal HTTP response log; the row insert itself still succeeds — Supabase never rolls back a transaction because a webhook failed. This means you can lose events if your endpoint has downtime and there's no retry queue in front of it, so treat webhooks as "best effort real-time" rather than a guaranteed delivery mechanism, especially for anything you can't afford to lose.
Should I use Database Webhooks or Realtime for this?
Database Webhooks are the right choice when you need to call an external HTTP endpoint (a serverless function, another SaaS tool, an automation platform). Supabase Realtime is for streaming row changes into a connected client, like updating a UI live in the browser. If you're pushing data out to a third-party system, use webhooks; if you're pushing updates into your own frontend, use Realtime.