Webhooks
Webhooks allow you to receive real-time notifications when events occur in your Fluxbase database. Instead of continuously polling for changes, webhooks push data to your application instantly.
Overview
Section titled “Overview”Fluxbase webhooks trigger HTTP requests to your specified endpoint whenever database events occur, such as:
- INSERT: A new row is added to a table
- UPDATE: An existing row is modified
- DELETE: A row is removed from a table
Using the SDK (Recommended)
Section titled “Using the SDK (Recommended)”Installation
Section titled “Installation”npm install @fluxbase/sdkQuick Start
Section titled “Quick Start”import { FluxbaseClient } from "@fluxbase/sdk";
// Initialize client (requires authentication)const client = new FluxbaseClient({ url: "http://localhost:8080", apiKey: process.env.FLUXBASE_CLIENT_KEY, // Or use service key for backend});
// Create a webhookconst webhook = await client.webhooks.create({ name: "User Events", description: "Notify external system of user changes", url: "https://example.com/webhooks/fluxbase", secret: "your-webhook-secret", enabled: true, events: [ { table: "users", operations: ["INSERT", "UPDATE"], }, ], max_retries: 3, timeout_seconds: 30,});
console.log("Webhook created:", webhook.id);
// List all webhooksconst webhooks = await client.webhooks.list();
// Get webhook detailsconst details = await client.webhooks.get(webhook.id);
// Update webhookawait client.webhooks.update(webhook.id, { enabled: false, events: [ { table: "users", operations: ["INSERT", "UPDATE", "DELETE"], }, ],});
// Delete webhookawait client.webhooks.delete(webhook.id);Managing Webhooks
Section titled “Managing Webhooks”Create a Webhook
Section titled “Create a Webhook”const webhook = await client.webhooks.create({ name: "Order Notifications", description: "Send notifications when orders are created or updated", url: "https://api.myapp.com/webhooks/orders", secret: "secure-random-secret-string", enabled: true, events: [ { table: "orders", operations: ["INSERT", "UPDATE"], }, { table: "order_items", operations: ["INSERT"], }, ], max_retries: 3, timeout_seconds: 30, retry_backoff_seconds: 5,});
console.log("Webhook ID:", webhook.id);console.log("Webhook URL:", webhook.url);Configuration Options:
| Field | Type | Description |
|---|---|---|
name | string | Descriptive name for the webhook |
description | string | Optional details about the webhook’s purpose |
url | string | The endpoint that will receive webhook events |
secret | string | Optional webhook secret for verifying requests |
enabled | boolean | Whether the webhook is active |
events | array | List of event configurations |
max_retries | number | Retry attempts for failed deliveries (default: 3) |
timeout_seconds | number | Request timeout in seconds (default: 30) |
retry_backoff_seconds | number | Seconds between retries (default: 5) |
Event Configuration:
{ table: "table_name", // Table to monitor operations: ["INSERT", "UPDATE", "DELETE"] // Events to trigger on}List Webhooks
Section titled “List Webhooks”const webhooks = await client.webhooks.list();
webhooks.forEach((webhook) => { console.log(`${webhook.name}: ${webhook.enabled ? "Enabled" : "Disabled"}`); console.log(` URL: ${webhook.url}`); console.log(` Events: ${webhook.events.length} configured`);});Get Webhook Details
Section titled “Get Webhook Details”const webhook = await client.webhooks.get("webhook-id");
console.log("Name:", webhook.name);console.log("Enabled:", webhook.enabled);console.log("Events:", webhook.events);console.log("Max Retries:", webhook.max_retries);console.log("Created:", webhook.created_at);Update Webhook
Section titled “Update Webhook”// Enable/disable webhookawait client.webhooks.update(webhookId, { enabled: false,});
// Update eventsawait client.webhooks.update(webhookId, { events: [ { table: "users", operations: ["INSERT", "UPDATE", "DELETE"], }, ],});
// Update URL and secretawait client.webhooks.update(webhookId, { url: "https://new-endpoint.com/webhooks", secret: "new-secret-key",});Delete Webhook
Section titled “Delete Webhook”await client.webhooks.delete(webhookId);console.log("Webhook deleted");View Delivery History
Section titled “View Delivery History”const deliveries = await client.webhooks.getDeliveries(webhookId, { limit: 50, offset: 0,});
deliveries.forEach((delivery) => { console.log(`${delivery.created_at}: ${delivery.status}`); console.log(` Response: ${delivery.response_status}`); console.log(` Attempts: ${delivery.attempts}`); if (delivery.error) { console.log(` Error: ${delivery.error}`); }});Verifying Webhook Signatures
Section titled “Verifying Webhook Signatures”Fluxbase signs all webhook requests with a timestamp-based HMAC-SHA256 signature to ensure authenticity and prevent replay attacks.
Signature Headers
Section titled “Signature Headers”When a webhook has a secret configured, Fluxbase sends two signature headers:
| Header | Format | Description |
|---|---|---|
X-Fluxbase-Signature | t=timestamp,v1=signature | Timestamped signature (recommended) |
X-Webhook-Signature | hexstring | Legacy signature (for backwards compatibility) |
Example Header:
X-Fluxbase-Signature: t=1705678901,v1=5d41402abc4b2a76b9719d911017c592How the Signature is Computed
Section titled “How the Signature is Computed”The signature is computed over timestamp.payload:
signed_data = timestamp + "." + raw_request_bodysignature = HMAC-SHA256(signed_data, webhook_secret)The timestamp is included to prevent replay attacks - signatures older than 5 minutes should be rejected.
Using the SDK (Recommended)
Section titled “Using the SDK (Recommended)”import { FluxbaseClient } from "@fluxbase/sdk";import express from "express";
const app = express();app.use(express.json());
const WEBHOOK_SECRET = process.env.FLUXBASE_WEBHOOK_SECRET!;
app.post("/webhooks/fluxbase", (req, res) => { const signature = req.headers["x-fluxbase-signature"] as string; const payload = req.body;
// Verify signature using SDK utility const isValid = FluxbaseClient.verifyWebhookSignature( payload, signature, WEBHOOK_SECRET );
if (!isValid) { return res.status(401).send("Invalid signature"); }
// Process the webhook event const { event, table, record } = payload; console.log(`Received ${event} event for ${table}`);
// Handle event...
res.status(200).send("OK");});
app.listen(3000);Manual Verification (Go)
Section titled “Manual Verification (Go)”import ( "github.com/fluxbase-eu/fluxbase/internal/webhook" "time")
func handleWebhook(body []byte, signatureHeader, secret string) error { // Verify with 5 minute tolerance err := webhook.VerifyWebhookSignature( body, signatureHeader, secret, 5*time.Minute, ) if err != nil { return fmt.Errorf("invalid signature: %w", err) } // Process webhook... return nil}Manual Verification (Node.js)
Section titled “Manual Verification (Node.js)”const crypto = require('crypto');
function verifyWebhookSignature(payload, header, secret, toleranceMs = 300000) { // Parse header: t=timestamp,v1=signature const parts = header.split(',').reduce((acc, part) => { const [key, value] = part.split('='); if (key === 't') acc.timestamp = parseInt(value, 10); if (key === 'v1') acc.signatures = [...(acc.signatures || []), value]; return acc; }, {});
// Check timestamp (replay protection) const now = Math.floor(Date.now() / 1000); if (Math.abs(now - parts.timestamp) > toleranceMs / 1000) { throw new Error('Signature timestamp too old or in future'); }
// Compute expected signature const signedPayload = `${parts.timestamp}.${JSON.stringify(payload)}`; const expectedSig = crypto .createHmac('sha256', secret) .update(signedPayload) .digest('hex');
// Compare signatures (constant-time) const isValid = parts.signatures.some(sig => crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expectedSig)) );
if (!isValid) { throw new Error('Signature mismatch'); }
return true;}Manual Verification (Python)
Section titled “Manual Verification (Python)”import hmacimport hashlibimport timeimport json
def verify_webhook_signature(payload: bytes, header: str, secret: str, tolerance_seconds: int = 300) -> bool: # Parse header: t=timestamp,v1=signature parts = {} signatures = [] for part in header.split(','): key, value = part.split('=', 1) if key == 't': parts['timestamp'] = int(value) elif key == 'v1': signatures.append(value)
# Check timestamp now = int(time.time()) if abs(now - parts['timestamp']) > tolerance_seconds: raise ValueError('Signature timestamp too old or in future')
# Compute expected signature signed_payload = f"{parts['timestamp']}.{payload.decode('utf-8')}" expected_sig = hmac.new( secret.encode('utf-8'), signed_payload.encode('utf-8'), hashlib.sha256 ).hexdigest()
# Compare signatures (constant-time) if not any(hmac.compare_digest(sig, expected_sig) for sig in signatures): raise ValueError('Signature mismatch')
return TrueWebhook Payload
Section titled “Webhook Payload”When an event occurs, Fluxbase sends a POST request to your webhook URL with the following payload:
{ "event": "INSERT", "table": "users", "schema": "public", "record": { "id": "550e8400-e29b-41d4-a716-446655440000", "email": "user@example.com", "created_at": "2025-11-02T10:30:00Z" }, "old_record": null, "timestamp": "2025-11-02T10:30:00.123Z", "webhook_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"}Payload Fields
Section titled “Payload Fields”| Field | Type | Description |
|---|---|---|
event | string | The operation type: “INSERT”, “UPDATE”, “DELETE” |
table | string | Name of the table where the event occurred |
schema | string | Database schema (usually “public”) |
record | object | The new/current state of the record |
old_record | object|null | Previous state (only for UPDATE and DELETE) |
timestamp | string | ISO 8601 timestamp when the event occurred |
webhook_id | string | UUID of the webhook configuration |
Event-Specific Payloads
Section titled “Event-Specific Payloads”INSERT Event
Section titled “INSERT Event”{ "event": "INSERT", "record": { /* new record data */ }, "old_record": null}UPDATE Event
Section titled “UPDATE Event”{ "event": "UPDATE", "record": { /* updated record data */ }, "old_record": { /* previous record data */ }}DELETE Event
Section titled “DELETE Event”{ "event": "DELETE", "record": { /* deleted record data */ }, "old_record": { /* same as record */ }}Receiving Webhooks
Section titled “Receiving Webhooks”const express = require("express");const { FluxbaseClient } = require("@fluxbase/sdk");
const app = express();app.use(express.json());
app.post("/webhooks/fluxbase", (req, res) => { // Verify signature const signature = req.headers["x-fluxbase-signature"]; const isValid = FluxbaseClient.verifyWebhookSignature( req.body, signature, process.env.FLUXBASE_WEBHOOK_SECRET );
if (!isValid) { return res.status(401).send("Invalid signature"); }
// Process event const { event, table, record, old_record } = req.body;
switch (event) { case "INSERT": console.log("New record:", record.id); break; case "UPDATE": console.log("Updated:", record.id); break; case "DELETE": console.log("Deleted:", record.id); break; }
res.status(200).send("OK");});
app.listen(3000);Best Practices
Section titled “Best Practices”| Practice | Description |
|---|---|
| Respond quickly | Return 200 status immediately, process asynchronously if needed (30s timeout) |
| Handle duplicates | Use record IDs to ensure idempotent processing |
| Verify signatures | Always verify webhook signatures using SDK utilities |
| Use HTTPS | Secure webhook URLs with HTTPS in production |
| Log deliveries | Keep detailed logs of events for debugging |
| Monitor failures | Use client.webhooks.getDeliveries() to track failed deliveries |
Example: Async processing
app.post("/webhooks", async (req, res) => { res.status(200).send("OK"); // Respond immediately processWebhookEvent(req.body).catch(console.error); // Process async});Common Use Cases
Section titled “Common Use Cases”- Send notifications: Trigger emails/push notifications on events
- Sync data: Keep external systems (Salesforce, CRM) in sync
- Analytics: Stream events to analytics platforms
- Trigger workflows: Start automated workflows based on database changes
Testing Webhooks
Section titled “Testing Webhooks”// Test via SDKawait client.webhooks.test(webhookId, { event: "INSERT", table: "test_table", record: { id: "test-id", name: "Test Record" }});
// Or use dashboard: Webhooks → Select webhook → TestMonitoring & Troubleshooting
Section titled “Monitoring & Troubleshooting”Monitor deliveries:
const deliveries = await client.webhooks.getDeliveries(webhookId, { limit: 100 });Retry behavior: Failed deliveries automatically retry (default: 3 attempts, exponential backoff, 30s timeout)
Common issues:
| Issue | Solution |
|---|---|
| Webhook not triggering | Verify table name, operations configured, webhook enabled |
| Delivery failures | Check endpoint is public, responds within timeout, valid SSL |
| Signature failing | Use correct secret, SDK’s verifyWebhookSignature(), raw body |
Learn More
Section titled “Learn More”- Authentication - Authenticate to manage webhooks
- Row Level Security - Secure your data
- Realtime - Alternative to webhooks for client apps