Debugging database webhooks and pg_net
Database webhooks or net.http_post calls that never reach your endpoint. How to read net._http_response, find timeouts and 401s, and test requests on SupaHost.
What you see
A database webhook or a net.http_post call in a trigger or cron job doesn't seem to do anything. Your endpoint logs show no request, or show an error, but the insert or the cron run that triggered it succeeded.
Why it happens
pg_net is asynchronous. net.http_post puts the request in a queue and returns an ID straight away. A background worker sends it after the transaction commits and writes the result to net._http_response. So the SQL that fired the request can't see the outcome, and failures end up only in that table.
Common causes:
- Timeout. Database webhooks default to 1000 ms, and
net.http_postdefaults to a few seconds. A slow endpoint getstimed_out = trueeven though it did the work. - Missing or wrong auth header. Your endpoint returns 401.
- The transaction rolled back. The request is only sent on commit.
- Bad URL or DNS.
error_msgshows a resolution or connection error. - The trigger isn't attached to the table or event you expected.
How to fix it
Look at recent responses. Failures have a 4xx/5xx
status_code, anerror_msgortimed_out:select id, status_code, timed_out, error_msg, left(content, 200) as body, created from net._http_response where status_code >= 400 or error_msg is not null or timed_out order by created desc limit 20;Responses are only kept for a few hours, so check soon after the event.
Check for requests still waiting to be sent:
select id, method, url, timeout_milliseconds from net.http_request_queue;This table holds headers too, including any keys you send. Any role that can read it can see them while a request waits.
On SupaHost a committed request normally leaves this table within about a second, when it's handed to pg_net's worker. A row that stays here for more than a minute usually belongs to a transaction that hasn't committed yet (or a long-running one). If rows pile up anyway, email [email protected] with the project name.
For database webhooks, match hook calls to responses:
select h.hook_name, h.created_at, r.status_code, r.timed_out, r.error_msg from supabase_functions.hooks h left join net._http_response r on r.id = h.request_id order by h.created_at desc limit 20;No rows in
supabase_functions.hooksmeans the trigger never fired. Check it exists:select tgname, tgenabled from pg_trigger where tgrelid = 'public.bookings'::regclass and not tgisinternal;Raise the timeout when your endpoint is slow:
select net.http_post( url := 'https://hooks.example.com/bookings', body := jsonb_build_object('booking_id', 42), timeout_milliseconds := 10000 );Better still, have the endpoint reply right away and do the slow work afterwards.
Test with a request you can inspect. Send to an echo service or a request bin, then read the response
contentto see exactly what was sent.
On SupaHost
pg_netand thesupabase_functionsschema are installed in every project database, so webhooks work without setup.- Calling your project's own APIs from the database uses the public URL, for example
https://app-one.acme.supahost.space/rest/v1/.... Requests to/rest/v1,/auth/v1and/storage/v1need anapikeyheader, or they get 401No API key found in request. /functions/v1/*doesn't need anapikey, but a function with Require JWT on needsAuthorization: Bearer <a project JWT>. Otherwise it returns 401UNAUTHORIZED_NO_AUTH_HEADER.- Keep keys in Vault, not in trigger arguments or cron commands, which anyone with read access to
cron.joborpg_triggercan see. - When the request reaches your function, look in Dashboard → your project → Functions tab → Logs for the other half of the story.