Supabase Database Webhooks: payload, pg_net, timeouts and testing

To test a Supabase Database Webhook, point it at a free webhooks.sh bin URL and insert a row: the payload with type, table, schema, record and old_record shows up live. No signup; a bin lasts 48 hours and takes 100 requests a day.

A Supabase Database Webhook sends an HTTP request with the changed row whenever a table gets an INSERT, UPDATE or DELETE. The quickest way to see the exact Supabase webhook payload is to point one at a free bin URL and change a row (no signup, lasts 48 hours, 100 requests a day). This page covers setup in the Dashboard or SQL, the payload, headers and keys, timeouts and pg_net, and debugging.

Point a Database Webhook at a bin

  1. Create a free bin in your browser at view.webhooks.page.
  2. Open Database Webhooks in the Supabase Dashboard and create a webhook: give it a name, pick the table, and select the events (insert, update, delete).
  3. Choose POST and paste your bin's ingest URL with a path such as /supabase/orders.
  4. Insert or update a row in that table and watch the payload show up live in the viewer.

Make sure you use POST. In Supabase's trigger function (supabase_functions.http_request), only POST builds the JSON row payload, while GET sends just URL parameters and headers without the row.

You can also create the same webhook in SQL with create trigger ... execute function supabase_functions.http_request(...), where the function arguments are the URL, method, headers, params and a timeout in milliseconds (timeout_ms).

create trigger "orders_to_bin"
after insert or update or delete on "public"."orders"
for each row
execute function "supabase_functions"."http_request"(
  'https://<bin>.webhooks.page/supabase/orders',
  'POST',
  '{"Content-Type":"application/json"}',
  '{}',
  '1000'
);

The payload for INSERT, UPDATE and DELETE

Every Supabase database webhook payload has the same five keys: type, table, schema, record and old_record. Which of record and old_record is null depends on the event (INSERT, UPDATE or DELETE). UPDATE is the useful one to capture first because it gives you the row before and after the change side by side.

FieldINSERTUPDATEDELETE
typeINSERTUPDATEDELETE
table, schemaTable and schema nameTable and schema nameTable and schema name
recordNew rowNew rownull
old_recordnullRow before the changeDeleted row
{
  "type": "UPDATE",
  "table": "orders",
  "schema": "public",
  "record": { "id": 42, "status": "paid" },
  "old_record": { "id": 42, "status": "pending" }
}

Headers, keys and what a bin shows

A standard Database Webhook sends Content-Type: application/json by default, plus any custom headers you add. If you pick the Supabase Edge Functions webhook type and click Add secret key under HTTP Headers, it includes the project's secret key, which the migration example sends in an apikey header.

A webhooks.sh bin automatically redacts headers whose name contains key, token, secret, authorization or cookie. That means apikey and Authorization show as redacted in the viewer and API while the request body with record and old_record stays fully visible.

Even with redaction, test with a throwaway value rather than your real secret key. Supabase also notes that a key added this way is saved straight into the trigger definition (so it shows up in database dumps), and any role that can log in to the database can read it from net.http_request_queue while the request waits to be sent.

Timeouts, pg_net and net._http_response

Database Webhooks are a wrapper around Postgres triggers that use the pg_net extension, which sends requests asynchronously from a background worker so network calls don't block your database changes. The webhook trigger function (supabase_functions.http_request) defaults to a 1000 ms timeout (timeout_ms), while pg_net's own http_post, http_get and http_delete functions default to 2000 ms.

Responses are kept for 6 hours in net._http_response. After a test, you can check that table to see the bin's HTTP 200 status and its small JSON reply ({"ok":true,"delivery_id":"..."}), which proves the request actually left the database. If you see a lot of requests timing out, raise the webhook's timeout in the Webhooks dashboard, since by default webhooks pick up the next 200 queued requests at a time (see our guide if a webhook is not received).

SettingValue
Database Webhook timeout default1000 ms
pg_net http_post timeout default2000 ms
Responses kept in net._http_response6 hours
pg_net intended rateUp to 200 requests per second
Queued requests picked up at a time200
MethodsPOST or GET (no PATCH or PUT)
-- Is the pg_net worker running?
select pid from pg_stat_activity
where backend_type ilike '%pg_net%';

-- Failed calls from the last 6 hours
select id, status_code, error_msg, created
from net._http_response
where status_code >= 400 or error_msg is not null
order by created desc;

Local development and free bin limits

When you run the local stack, the Supabase database is inside Docker, so localhost and 127.0.0.1 in a webhook URL point at the database container itself. Use host.docker.internal to reach a server running on your host machine, or read more about how to test webhooks locally. Because a bin is a public HTTPS URL on port 443, both a hosted Supabase project and your local Docker stack can reach it directly.

If you're testing with a free bin, plan around its limits:

  • It takes 100 requests a day (resetting at midnight UTC) and returns 429 daily_cap_reached with a Retry-After header after that.
  • Request bodies over 100KB get refused with 413 body_too_large.

A busy table will burn through 100 requests fast, so point your webhook at a quiet table or a staging project. Any 429 or 413 responses from the bin show up in net._http_response just like any other HTTP status code.

Keep the bin or relay to your machine

A free bin expires after 48 hours and its viewer shows the latest 10 requests. You can claim a bin from the bin page with a free Google sign-in before it expires: it keeps the same webhooks.page URL, stops the expiry timer, and stores new requests for 30 days on a rolling basis, while keeping the 100 requests a day and 100KB body limits.

If you need more volume or want to hit a local server from a hosted Supabase project, Solo ($12 a month) gives you a persistent endpoint on webhooks.host with 250,000 requests per billing period and request bodies up to 5MB. It also includes the wsh CLI, which relays live webhook calls from your hosted project straight to your local server (wsh listen <endpoint> --to localhost:3000) and replays captured requests to localhost.

FAQ

How do I test a Supabase database webhook?

Create a free bin, set the bin's ingest URL as your webhook URL using POST, and insert or update a row in your table. The request and payload show up live in your browser. Free bins need no signup, last 48 hours, and take 100 requests a day.

What payload does a Supabase database webhook send?

It sends a JSON body with five keys: type (INSERT, UPDATE or DELETE), table, schema, record and old_record. On DELETE, record is null and old_record holds the deleted row; on INSERT, old_record is null and record holds the new row.

What is the default timeout for Supabase database webhooks?

The webhook trigger function (supabase_functions.http_request) defaults to 1000 ms (timeout_ms), while pg_net's own functions default to 2000 ms. You can raise the timeout in the Webhooks dashboard if your requests time out.

Why are my Supabase webhooks timing out?

Check that the pg_net background worker is running, query net._http_response for error messages, and raise the webhook's timeout in the dashboard so queued batches of 200 requests have enough time to finish. Also update Postgres if your pg_net version is older than v0.11 (available in Postgres 15.6.1.135 and above), which patched a timeout bug.

Where can I see Supabase webhook logs?

Look in the net schema inside your database. net._http_response keeps response messages and status codes for 6 hours, and net.http_request_queue holds requests that are waiting to be sent.

Can a Supabase webhook call localhost?

On the local stack, localhost points at the database's own Docker container, so use host.docker.internal to reach your machine. A hosted Supabase project needs a public URL, like a free bin or a Solo endpoint relayed to your machine with the wsh CLI (wsh listen <endpoint> --to localhost:3000).

Can Supabase database webhooks use GET?

Yes, Supabase supports POST and GET webhooks. However, in the supabase_functions.http_request trigger function only POST builds and sends the JSON row payload, while GET sends just the URL parameters and headers.

Last updated

Try it in 10 seconds.

Create a free bin at view.webhooks.page — no signup, no install. Point your sender at the URL and watch requests land live. Bins expire after 48 hours.