SQL triggers
Evaluate SQL on a schedule and send query results through managed destinations.
SQL triggers
A trigger evaluates read-only SQL at a fixed interval. Every returned row is a
match; use WHERE or HAVING in the SQL to express the condition. Each execution
sends one array containing all returned rows to each destination. An empty result
sends nothing. Repeated results and duplicate rows are sent again; consumers own
any deduplication or alerting policy.
Triggers require an enabled trigger service. Organization administrators can create, run, edit, pause, and delete triggers. Organization members can view their configuration, but HTTP URLs and header values are never returned.
Configure destinations
Open a trigger in the dashboard and add up to five destinations:
- HTTP sends the event to a webhook. Enter a name, URL, and optional custom
headers. The URL and header values are encrypted and write-only. On edit,
leave the URL blank and configured header values blank to preserve them.
URLs and header values cannot contain
{{,%, or${: these are reserved by the delivery configuration. Percent-encoded URLs are not currently supported. - Table inserts the returned rows to a RawTree table. Select its database and table; RawTree creates and manages the scoped write-only sink key under the hood.
The equivalent API request is:
curl -X POST "$RAWTREE_URL/v1/triggers?organization=$ORGANIZATION&cluster=$CLUSTER" \
-H "Authorization: Bearer $SESSION_TOKEN" \
-H 'Content-Type: application/json' \
--data-binary @- <<'JSON'
{
"name": "high-volume-customers",
"database": "default",
"sql": "WITH accurateCastOrNull(customer_id, 'String') AS customer_key SELECT customer_key AS customer_id, count() AS total FROM events WHERE customer_key IS NOT NULL GROUP BY customer_key HAVING total > 100",
"interval_ms": 5000,
"enabled": true,
"destinations": [
{
"type": "http",
"name": "Operations webhook",
"url": "https://example.com/events",
"headers": {"Authorization": "Bearer YOUR_DESTINATION_TOKEN"}
},
{
"type": "table",
"name": "Alert archive",
"database": "default",
"table": "trigger_alerts"
}
]
}
JSONOmit a destination id when adding it; RawTree assigns one. Preserve that ID
when editing. PATCH replaces the destination list when destinations is
supplied; omitting it preserves the existing list. Send "destinations": []
to remove all destinations.
Event format and delivery
Both destination types receive the plain JSON array returned by the SQL:
[
{"customer_id": "customer-a", "total": 101},
{"customer_id": "customer-b", "total": 125}
]Two matching rows and two destinations produce two requests, each containing that complete array. A table destination inserts both rows in one request.
RawTree hands these results to its managed Vector data plane. Vector uses durable
disk buffers and retries retryable HTTP failures. HTTP requests include
X-RawTree-Event-Id, X-RawTree-Destination-Id, and an Idempotency-Key of
rawtree/event_id/destination_id. Delivery is at-least-once, so receivers should
deduplicate using that key.
Each scheduled run attempts SQL once. A query or handoff failure is recorded in Logs; the next scheduled run evaluates independently. Once accepted by Vector, destination retries reuse the buffered array without executing SQL again. Delivery IDs are scoped to the execution and destination, so the next execution has a new ID even when its rows are identical.
The API rejects explicit private, loopback, link-local, and metadata addresses unless the operator has allowed the exact origin. Delivery also checks resolved IP addresses and does not follow redirects. Vector owns buffering and retries; RawTree's delivery endpoint performs and observes each destination request. Pausing a trigger stops new evaluations.
Inspect a trigger's lifecycle
The editor has four tabs:
| Tab | What it shows |
|---|---|
| SQL | The exact SQL sent through the Query API and its native result or error |
| Settings | Schedule and enabled state |
| Destinations | HTTP and table delivery targets |
| Logs | Saved evaluations, returned rows, handoffs, and destination attempts |
Logs become available after saving a trigger. Choose a time range, refresh to see new activity, or load older records. Expand a returned row to inspect its data. Expand correlation IDs to connect an evaluation run, logical match, and delivery attempt. Retries keep the same match event ID but get separate attempt IDs. Each destination attempt includes its type, HTTP status when available, duration, and a safe failure code.
Evaluation started → evaluation result / returned rows
↓
Vector handoff accepted
↓
Destination attempt
↙ ↘
Accepted Failed → retry, if retryableAn accepted Vector handoff is not a successful destination delivery. A successful delivery means the destination returned a 2xx response, not that its application finished processing the event. A timeout leaves delivery uncertain; the receiver may have accepted the event before the timeout.
The same history is available through the scoped API:
curl "$RAWTREE_URL/v1/triggers/$TRIGGER_ID/logs?organization=$ORGANIZATION&cluster=$CLUSTER&limit=100" \
-H "Authorization: Bearer $SESSION_TOKEN"The response contains logs, from, to, and next_cursor. Pass the returned
from, to, and next_cursor as cursor for a stable older page. from and
to are Unix milliseconds; requests default to the last 24 hours and support
ranges up to seven days, including older historical ranges. limit is 1–200.
History is stored in the existing internal.trigger_events table and remains
best-effort observability, not an exactly-once audit ledger. Writes may be delayed,
duplicated, or dropped during overload or outages. A process crash can leave a
started operation without a recorded result. Matching state remains
transactional in PostgreSQL and does not depend on these logs; Temporal
completion results carry only compact version and count metadata. Existing
evaluation records remain readable; delivery history starts after the updated
services are deployed.
Webhook URLs, headers, and receiver response bodies are not included. Matched
keys and rows are recorded only on the new-match record and are visible to
members of the owning organization. Payloads larger than 16 KiB are omitted
from history with payload_omitted: true; the destination still receives the
full event. Each destination event is delivered in a separate request so every
attempt has an unambiguous result.