supabase_functions.http_request()) plus pg_net (the async HTTP extension) — both preloaded in every Powabase project. This is the pattern for “tell our internal API every time a user signs up,” “post to Slack on order creation,” or “invalidate a cache when this row updates.”
This is NOT the same as the agentic webhooks at /api/webhooks. Those are the inbound side: external systems triggering workflows. Database webhooks are the outbound side: Postgres rows changing and Postgres calling out to some HTTP endpoint. Don’t confuse them.
For the agentic surface, see Webhooks reference. For pg_net specifics, see Extensions.
How it works
The platform installspg_net in the extensions schema and a helper trigger function supabase_functions.http_request() that wraps pg_net.http_post. You attach the function as a trigger to your table:
http_request:
- URL — where to POST.
- Method — typically
POST;PUT/PATCH/DELETEalso work. - Headers (JSONB) — your auth, content-type, custom headers.
- Params (JSONB) — query string params, if any.
- Timeout (ms) — how long pg_net waits before giving up.
NEW row for INSERT/UPDATE triggers, serialized as JSON. For DELETE triggers, it’s the OLD row. The function doesn’t let you customize the body — see below for that.
Customizing the body
The default body is the bare row. For richer payloads, write your own trigger function that wrapspg_net.http_post:
pg_net.http_post returns a request_id. The actual HTTP response (or error) lands later in the net._http_response table — async.
Async semantics
pg_net is fire-and-forget from the trigger’s perspective. The HTTP request runs in a background worker; the trigger function returns immediately after queuing it. Two implications:
- The trigger doesn’t block on the HTTP response. Your INSERT commits as soon as the trigger queues the request. The HTTP call can fail without rolling back the INSERT.
- You can’t get the response synchronously. If your trigger needs to know whether the call succeeded (for retry logic, for a transactional outbox pattern), you have to poll
net._http_response.
Querying responses
To see how recent webhook calls went:status_code is the HTTP response code (200, 4xx, 5xx). error_msg is populated on timeouts and connection errors.
The net._http_response table accumulates rows over time and isn’t auto-pruned. For high-volume webhook senders, run a periodic cleanup:
Retries
pg_net does not retry failed requests. If your endpoint is down when the trigger fires, the request is lost.
Three retry patterns, picking depending on what you need:
Option 1: Application-level retry on the receiving end. The webhook arrives once; if the receiver wants idempotency, it dedupes by an event id you include in the payload.
Option 2: Outbox table + scheduled retry. Insert “I want to send X” rows into a public.webhook_outbox table inside the same transaction as the INSERT that triggers it. A scheduled workflow drains the outbox, calls the webhook, and marks rows as sent (or failed-retry).
Option 3: Use the agentic /api/webhooks surface in the other direction. Have your trigger call a deployed workflow URL; the workflow handles retries and observability inside Powabase. This trades async-HTTP simplicity for workflow visibility.
Most webhooks should be retryable on the receiver side (option 1). Reserve options 2/3 for cases where you need delivery guarantees.
Common patterns
Slack notification on row insert
orders row serialized as JSON. Slack’s webhook format expects { "text": "..." } — this won’t format nicely. Use a custom function (Pattern: “Customizing the body” above) to build a Slack-shaped payload.
Cache invalidation on update
Audit log to S3
For row-change auditing where you want to ship every row change off-platform, point the webhook at an HTTPS endpoint that writes to S3 (a Lambda, a small webhook service, your own ingestion pipeline). The async nature of pg_net means your INSERT doesn’t wait — the audit writes happen on the side.DB webhooks vs Realtime postgres_changes
Both fire on row changes. The difference:| DB webhook | Realtime postgres_changes | |
|---|---|---|
| Transport | HTTP POST | WebSocket |
| Receiver | Any HTTP endpoint | Connected clients |
| Persistence | None (pg_net queues, but no delivery guarantee) | None (WS subscribers see live changes only) |
| Retries | None built-in | None |
| Use case | Backend-to-backend integration | Client UI updates |
Next steps
Webhooks reference
The inbound side — external systems triggering workflows via /api/webhooks. Different surface, easily confused.
Realtime model
The browser-friendly alternative for change notifications.
Extensions
pg_net and what else lives in the extensions schema.
Direct Postgres
For installing triggers via psql or migrations.