Webhooks
Four modes for letting outside systems trigger work in SupaNet — save to table, call a function, run an agent, or run a prompt.
A webhook lets an external system POST data into SupaNet and have something
happen. Each webhook is a row with an opaque token; callers hit the public
webhook edge function at /functions/v1/webhook/<token>.
Four modes, in precedence order
When a payload arrives, the webhook function decides what to do based on how the webhook is configured. Exactly one target is chosen (they are mutually exclusive). The precedence is:
- Save to table (
table_id+target_column) - deterministic, no model. The raw JSON payload drops straight into one column of a user table — it never skips, never gets lost, and the AI has no say. If the target table is an event source (hasemit_eventsenabled), the row insert triggers atable.*event that any listener can react to. This is the "append-only ingestion" pattern: external data flows in, a database fact is recorded, and then intelligent processing happens downstream via listeners. - Direct tool (
tool_id) - deterministic, no model. The payload is validated against the tool's input schema (required fields and top-level types), then POSTed straight to the tool's URL. A bad payload returns a 400 listing the offending fields. This is the "n8n function node" pattern: schema validation is the gate, because nothing reaches an LLM. - Agent (
agent_id) - the payload runs through an agent (its prompt and tools) via the agent loop. - Prompt - the payload runs against a plain prompt.
Per-table event sourcing
When you enable "Trigger an event when a row is added" on a table (via Settings),
the table becomes an event source. Every new row insert fires a table.<slug>_<id>
event carrying the full row as data. The event is automatically scoped to the table's
visibility (private or workspace).
Each table gets a stable, frozen event type — once minted, toggling events on/off preserves it, so listeners wired to the event name never break from a pause/resume.
The webhook → table → listener flow:
- Webhook saves raw payload to table (deterministic, no guardrail).
- Row insert fires a
table.*event. - A listener matches the event and runs an agent (with the event data as context) to structure, classify, or act on the raw data.
- The agent files the processed result into collections for your team.
This decouples ingest from processing: raw data always lands, processing can be retried/modified/scoped without changing the webhook, and the event log gives you a full audit trail.
Read-only by default
A webhook-targeted agent runs read-only unless allow_tools = true. Tools are
only loaded when that flag is set - a deterministic rule in code, not a model
decision - so an untrusted source cannot make the agent take actions.
Guardrails on the LLM paths
The agent and prompt modes run a guardrail
pre-flight, and webhooks fail closed: if the guardrail errors or blocks, the
run is stopped and logged as blocked (HTTP 403). The direct-tool mode skips
this, because no LLM is involved - schema validation already gates it.
Optional shared secret
The URL token alone is "secret URL" security. For real authentication, set a
secret on the webhook. Callers must then present it as
Authorization: Bearer <secret> or X-Webhook-Secret: <secret>, or the function
returns 401 before logging anything - so a wrong or missing secret cannot spam
the event log. A null secret means no secret (unchanged behaviour). It is a
plaintext shared secret on the row, the same trust model as the token.
The event log
Every call writes a webhook_events row (received → ok / error /
blocked) with the result. The Webhooks page subscribes to these over Realtime
for a live log. Outcomes also land in the activity log.
Pairing with Forge
Because forged functions are http tools, the editor's
"call a function directly" picker lists them, shows their fields and types, and
renders a sample payload - making the deterministic direct-tool mode easy to wire
up.