Webhooks and integrations
Postgres functions cannot safely call external systems, and calling them from the browser is not safe either. PotaLab Base gives each project a transactional outbox:
- A trigger or function enqueues a message in the same transaction as your data change.
- After commit, Base delivers it and retries on failure.
- A rolled-back transaction sends nothing. A committed one is not lost.
The outbox is enabled automatically when you create a webhook or configure SMTP.
Database webhooks
Section titled “Database webhooks”Create webhooks in the dashboard (Integrations → Webhooks) or with the Management API:
POST /v1/projects/{ref}/webhooks{ "name": "order created", "table": "public.orders", "events": ["INSERT"], "url": "https://hooks.example.com/orders", "headers": { "Authorization": "Bearer {{secret:HOOK_TOKEN}}" }, "timeout_ms": 10000 }The response includes a signing_secret (whsec_...) shown once; rotate it with PATCH and { "rotate_secret": true }.
- Payload:
{ type, schema, table, record, old_record, webhook_id, occurred_at }. A row larger than 1 MB is sent withtruncated: true. - Signature: requests follow Standard Webhooks. The
webhook-id,webhook-timestampandwebhook-signatureheaders are reserved. Verify them with any Standard Webhooks library and yourwhsec_...secret. - URL:
http(s)://.... A literal private IP address is refused (400 url_not_allowed). - Works on tables in
publicandapi.
Outbound HTTP from SQL
Section titled “Outbound HTTP from SQL”perform base.http_request( method => 'POST', url => 'https://api.example-payments.com/v1/refunds', headers => jsonb_build_object('Authorization', 'Bearer {{secret:PAYMENT_API_KEY}}'), body => jsonb_build_object('charge', v_charge_id), callback_function => 'public.on_refund_response', -- optional timeout_ms => 10000);Delivery is fire-and-forget: the response never reaches the calling transaction. API roles cannot call it, so wrap it in a SECURITY DEFINER function or trigger. An optional callback schema.fn(jsonb) receives { id, status, delivered, body, error, attempts } after the final outcome and must be granted to base_callback:
grant execute on function public.on_refund_response(jsonb) to base_callback;Transactional email
Section titled “Transactional email”perform base.send_email(recipient => new.email, subject => 'Order ' || new.id, html => '<p>Thanks!</p>', text_body => 'Thanks!');Mail uses your project’s SMTP settings (owner only, Settings → Email; custom SMTP needs a plan with custom_smtp) or the platform default. Auth emails are sent directly and do not use the outbox.
Secrets
Section titled “Secrets”PUT /v1/projects/{ref}/secrets/PAYMENT_API_KEY { "value": "sk_live_..." }GET /v1/projects/{ref}/secrets -> names and timestamps onlyDELETE /v1/projects/{ref}/secrets/PAYMENT_API_KEYNames match [A-Za-z_][A-Za-z0-9_]{0,63}, values are at most 16 KB and are encrypted at rest and never readable. Use {{secret:NAME}} in a URL, header or body and it is substituted in memory at send time. No SQL function returns a secret.
Retries and delivery states
Section titled “Retries and delivery states”| Outcome | Result |
|---|---|
| 2xx | delivered |
| 5xx, 408, 409, 425, 429, network error, unknown secret | failed, retried with exponential backoff (at most 6 h between tries) |
| other 4xx, blocked target, invalid URL, deleted webhook | dead immediately |
| 8 attempts, or 24 h since creation | dead |
Inspect deliveries in Integrations → Deliveries or with GET /v1/projects/{ref}/outbox/deliveries?status=failed, and retry with POST .../outbox/deliveries/{id}/retry.
Inbound third-party webhooks, cron jobs and queues are not available.