How-to guide

Trigger Queuey from Supabase database webhooks

A Supabase Database Webhook fires an HTTP request whenever a row changes. Point it at your Queuey ingress URL and every insert, update or delete becomes a durable event — with retries, a dead-letter queue, ordering and full delivery history — before it reaches whatever should react to it. No SDK, no code: connect the project from the console and Queuey installs the webhook for you — or set it up by hand with one URL and one header.

Connect automatically (recommended)

The console can install the webhook for you. Open Integrations in the Queuey console, click Connect on Supabase, and approve the access request on supabase.com (it is an organization-level grant — pick the organization that owns the project). Then choose a workspace, a project and one or more tables, tick the operations you want (insert, update, delete), and click Create queue & install trigger. For each table Queuey:

  • creates a queue named after the table (orders for public.orders) that requires an API key — a request without it is refused with 401;
  • mints a key that can publish to that one queue only, so a leaked trigger header cannot reach anything else you own;
  • installs a Database Webhook trigger named queuey_<table> in your project — visible under Integrations → Database Webhooks like any hook you made by hand, with a 5-second timeout;
  • labels every event with the operation (INSERT, UPDATE, DELETE) read from the body, so the console and your delivery rules can tell them apart.

The queue starts in Log-only: change a row, watch it arrive, then set a destination under the queue’s Delivery settings when you are ready. Supabase itself does not retry the request it sends to Queuey; everything after the 202 is durable. Bodies above roughly 64 KB are not sent by Supabase’s pg_net at all — keep wide rows out of webhook tables or send ids and let the receiver fetch.

Removing and disconnecting
Remove on a table drops the trigger and revokes its key; the queue and its events are kept until you delete the queue yourself. Disconnect does that for every table under the connection and then revokes the token grant. If you revoke the grant on supabase.com instead (Organization → Integrations), the console shows the connection as Revoked: Queuey can no longer reach the project to remove its triggers, so Disconnect revokes their keys — a stranded trigger can only get 401 — and you drop the queuey_* triggers yourself. Queuey never stores your database credentials: the grant is an OAuth token, encrypted at rest, used only to list your projects and tables, run the trigger SQL, and check that the trigger still exists.

Or by hand: 1 — Create the webhook

Prefer to keep Queuey out of your Supabase organization? The manual route is one URL and one header. In the Supabase dashboard, open Integrations → Database Webhooks (the first time, click Install integration — it enables the pg_net extension), then Create a new hook:

webhook configuration
Name:      orders-to-queuey
Table:     public.orders
Events:    Insert, Update, Delete
Type:      HTTP Request
Method:    POST
URL:       https://ingress.queuey.ai/events/ten_yourTenant/orders
Headers:   Content-Type: application/json
           X-Api-Key: qak_8fK2mNpQ.aB3dE7gH9jK1mN4pR6tV8wX0yZ

The URL and the X-Api-Key value come from your Queuey console — see the quickstart if you have neither yet. That is the whole integration: Supabase delivers the row change to Queuey, and Queuey answers 202 Accepted once the event is durably stored.

2 — What arrives on the queue

Supabase sends a fixed JSON shape: the operation, the table, and the row (old_record is filled on updates and deletes). Queuey accepts it as-is — no envelope required:

what Supabase sends
{
  "type": "INSERT",
  "table": "orders",
  "schema": "public",
  "record": {
    "id": 1042,
    "customer_id": "ACME",
    "status": "placed",
    "created_at": "2026-08-31T14:38:36.69Z"
  },
  "old_record": null
}
what Queuey answers
HTTP/1.1 202 Accepted
Content-Type: application/json

{
  "queuePublicId": "que_Rp2yPELnp4sr",
  "eventId": "evt_nd9qgy1sjPIW",
  "receivedAtUtc": "2026-08-31T14:38:36.71Z",
  "mode": "Deliver",
  "replayed": false
}

From here the event behaves like any other Queuey event: it shows up in the console, delivers to the queue’s target with the queue’s retry policy, and lands in the dead-letter queue instead of disappearing when the target will not take it.

mode: LogOnly? Give the queue a target
A fresh queue has no delivery target, so it accepts and stores events without forwarding them — the ack says "mode": "LogOnly". To deliver, open the queue in the console, set its endpoint under Delivery, and switch the queue to Deliver. Events accepted while the queue was log-only stay inspectable in the console.

3 — Telling operations apart

The operation travels in the body: type says whether the row was inserted, updated or deleted, and table says where. Your receiver branches on those two fields. When different tables deserve different handling — separate targets, separate retry policies, separate ordering — give each table its own webhook pointing at its own queue (orders, invoices, …) rather than parsing everything out of one stream.

Do not set a static Idempotency-Key header
An Idempotency-Key header on the webhook would carry the same value on every row change — and Queuey would correctly deduplicate all but the first event away. Leave it unset. Supabase’s delivery can occasionally duplicate a request; make the final receiver idempotent instead (reliable delivery) — a duplicate you can ignore beats a row change you never heard about.

Prefer SQL? The webhook is a trigger

Dashboard webhooks are ordinary Postgres triggers under the hood. If you keep your schema in migrations — or you are letting an AI agent set the project up — create the trigger directly:

migration-friendly version
-- The dashboard webhook is just a trigger. Creating it in SQL means it
-- lives in your migrations and survives a project rebuild.
create trigger orders_to_queuey
  after insert on public.orders
  for each row
  execute function supabase_functions.http_request(
    'https://ingress.queuey.ai/events/ten_yourTenant/orders',
    'POST',
    '{"Content-Type":"application/json","X-Api-Key":"qak_…"}',
    '{}',
    '5000'  -- timeout in ms; the 202 ack is fast, but do not go lower
  );

Why not point Supabase straight at your receiver?

You can — and it works until the receiver has a bad minute. Database Webhooks are sent via pg_net: fire-and-forget, with a short timeout, no durable retry, no dead-letter queue, and no way to see afterwards what was sent or what failed. If your endpoint is down, deploying, or rate-limited, that row change is simply gone. Putting Queuey’s ingress in between turns the fragile hop into the shortest one — a fast 202 from an endpoint built to say yes — and every hop after that is retried, observable and replayable.

Related