Skip to content
Beta — Truss is in public beta. Documentation is actively updated but may not reflect the latest changes. Report issues on GitHub.

Webhooks

Truss webhooks send HTTP POST requests to your URLs when database rows change. They use the same LISTEN/NOTIFY infrastructure as realtime subscriptions, with added features like HMAC signing, retry tracking, and delivery logs.

Navigate to Webhooks in the sidebar. Click “Create Webhook” and configure:

  • Name — a label for the webhook
  • Table — which table to watch
  • Events — INSERT, UPDATE, DELETE (pick one or more)
  • URL — the endpoint to call
  • Headers — optional custom headers (e.g., Authorization)
  • Secret — for HMAC signature verification
Terminal window
curl -X POST http://localhost:8787/api/webhooks \
-H "Content-Type: application/json" \
-d '{
"name": "New order notification",
"table_schema": "public",
"table_name": "orders",
"events": ["INSERT"],
"url": "https://your-app.com/webhooks/new-order",
"headers": {"Authorization": "Bearer your-secret"},
"secret": "whsec_your_signing_secret",
"active": true
}'

When an event fires, Truss sends a POST request with this JSON body:

{
"event": "INSERT",
"schema": "public",
"table": "orders",
"row": {
"id": 42,
"user_id": 1,
"total": 99.99,
"created_at": "2025-01-15T10:00:00Z"
},
"old_row": null,
"timestamp": "2025-01-15T10:00:00.123Z"
}

For UPDATE events, both row (new data) and old_row (previous data) are included.

If you set a secret on the webhook, Truss signs each payload with HMAC-SHA256. The signature is sent in the X-Truss-Signature header as a hex-encoded string.

  1. Truss serializes the JSON payload to a string
  2. Computes HMAC-SHA256(payload_string, your_secret)
  3. Hex-encodes the result
  4. Sends it in the X-Truss-Signature header
import crypto from 'crypto';
import express from 'express';
function verifySignature(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express middleware
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-truss-signature'];
if (!sig || !verifySignature(req.body, sig, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
console.log('Verified event:', event);
res.sendStatus(200);
});

Always use constant-time comparison (timingSafeEqual in Node.js, hmac.compare_digest in Python, hmac.Equal in Go) to prevent timing attacks.

Terminal window
# List all webhooks
curl http://localhost:8787/api/webhooks
# Update a webhook
curl -X PATCH http://localhost:8787/api/webhooks/{id} \
-H "Content-Type: application/json" \
-d '{"active": false}'
# Delete a webhook
curl -X DELETE http://localhost:8787/api/webhooks/{id}

Each webhook has an active boolean. Disabled webhooks remain configured but do not fire when events occur. This is useful for temporarily pausing a webhook while debugging your endpoint.

Terminal window
# Disable a webhook
curl -X PATCH http://localhost:8787/api/webhooks/{id} \
-H "Content-Type: application/json" \
-d '{"active": false}'
# Re-enable
curl -X PATCH http://localhost:8787/api/webhooks/{id} \
-H "Content-Type: application/json" \
-d '{"active": true}'

From the dashboard, toggle the switch next to any webhook to enable or disable it instantly.

Terminal window
curl -X POST http://localhost:8787/api/webhooks/{id}/test

Sends a synthetic test payload to the webhook URL and returns the HTTP response status, response body, and latency. The test payload uses event: "TEST" so your handler can distinguish test events from real ones.

Test events appear in the delivery logs alongside real deliveries.

When developing locally, your webhook endpoint might not be publicly accessible. Options:

  • Use a tunneling service (ngrok, Cloudflare Tunnel) to expose your local server
  • Point the webhook URL to http://host.docker.internal:3000/webhook if running in Docker
  • Use the test endpoint to verify payload format, then test your handler with cURL locally

Every webhook delivery (including failures and test events) is logged for inspection.

Terminal window
curl http://localhost:8787/api/webhooks/{id}/logs

Each log entry includes:

  • event_type — INSERT, UPDATE, DELETE, or TEST
  • payload — the full JSON that was sent
  • status_code — HTTP response status (or null if the request timed out)
  • response_body — the first 1 KB of the response from your server
  • latency_ms — round-trip time in milliseconds
  • created_at — when the delivery was attempted
Terminal window
curl -X POST http://localhost:8787/api/webhooks/{id}/replay/{logId}

Re-sends the exact same payload from a previous delivery. This is useful when:

  • Your endpoint was down and you need to reprocess missed events
  • You fixed a bug in your handler and want to reprocess a failed delivery
  • You want to test idempotency of your handler

The replayed delivery creates a new log entry so you can compare the original and replayed results.

The Webhooks panel shows delivery logs per webhook with color-coded status badges:

  • Green (2xx) — successful delivery
  • Yellow (3xx/4xx) — client error or redirect
  • Red (5xx / timeout) — server error or unreachable

Truss tracks consecutive failures per webhook via fail_count. The dashboard shows which webhooks are healthy and which are failing. You can pause failing webhooks and resume them after fixing the endpoint.

Webhooks are available via the management API:

Terminal window
curl http://localhost:8787/v1/webhooks \
-H "apikey: truss_sk_your_key"
curl http://localhost:8787/v1/webhooks/{id} \
-H "apikey: truss_sk_your_key"
Terminal window
# Create a webhook via Truss API
curl -X POST \
${TRUSS_API_URL}/api/webhooks \
-H "Content-Type: application/json" \
-b "ory_kratos_session=${SESSION_TOKEN}" \
-d '{
"name": "Order notifications",
"table_schema": "public",
"table_name": "orders",
"url": "https://example.com/webhook",
"events": ["INSERT", "UPDATE", "DELETE"],
"secret": "whsec_your_signing_secret"
}'
# Test a webhook
curl -X POST \
${TRUSS_API_URL}/api/webhooks/WEBHOOK_ID/test \
-b "ory_kratos_session=${SESSION_TOKEN}"