Skip to content

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.

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

Terminal window
npm install @fluxbase/sdk
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 webhook
const 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 webhooks
const webhooks = await client.webhooks.list();
// Get webhook details
const details = await client.webhooks.get(webhook.id);
// Update webhook
await client.webhooks.update(webhook.id, {
enabled: false,
events: [
{
table: "users",
operations: ["INSERT", "UPDATE", "DELETE"],
},
],
});
// Delete webhook
await client.webhooks.delete(webhook.id);

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:

FieldTypeDescription
namestringDescriptive name for the webhook
descriptionstringOptional details about the webhook’s purpose
urlstringThe endpoint that will receive webhook events
secretstringOptional webhook secret for verifying requests
enabledbooleanWhether the webhook is active
eventsarrayList of event configurations
max_retriesnumberRetry attempts for failed deliveries (default: 3)
timeout_secondsnumberRequest timeout in seconds (default: 30)
retry_backoff_secondsnumberSeconds between retries (default: 5)

Event Configuration:

{
table: "table_name", // Table to monitor
operations: ["INSERT", "UPDATE", "DELETE"] // Events to trigger on
}
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`);
});
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);
// Enable/disable webhook
await client.webhooks.update(webhookId, {
enabled: false,
});
// Update events
await client.webhooks.update(webhookId, {
events: [
{
table: "users",
operations: ["INSERT", "UPDATE", "DELETE"],
},
],
});
// Update URL and secret
await client.webhooks.update(webhookId, {
url: "https://new-endpoint.com/webhooks",
secret: "new-secret-key",
});
await client.webhooks.delete(webhookId);
console.log("Webhook deleted");
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}`);
}
});

Fluxbase signs all webhook requests with a timestamp-based HMAC-SHA256 signature to ensure authenticity and prevent replay attacks.

When a webhook has a secret configured, Fluxbase sends two signature headers:

HeaderFormatDescription
X-Fluxbase-Signaturet=timestamp,v1=signatureTimestamped signature (recommended)
X-Webhook-SignaturehexstringLegacy signature (for backwards compatibility)

Example Header:

X-Fluxbase-Signature: t=1705678901,v1=5d41402abc4b2a76b9719d911017c592

The signature is computed over timestamp.payload:

signed_data = timestamp + "." + raw_request_body
signature = HMAC-SHA256(signed_data, webhook_secret)

The timestamp is included to prevent replay attacks - signatures older than 5 minutes should be rejected.

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);
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
}
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;
}
import hmac
import hashlib
import time
import 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 True

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"
}
FieldTypeDescription
eventstringThe operation type: “INSERT”, “UPDATE”, “DELETE”
tablestringName of the table where the event occurred
schemastringDatabase schema (usually “public”)
recordobjectThe new/current state of the record
old_recordobject|nullPrevious state (only for UPDATE and DELETE)
timestampstringISO 8601 timestamp when the event occurred
webhook_idstringUUID of the webhook configuration
{
"event": "INSERT",
"record": {
/* new record data */
},
"old_record": null
}
{
"event": "UPDATE",
"record": {
/* updated record data */
},
"old_record": {
/* previous record data */
}
}
{
"event": "DELETE",
"record": {
/* deleted record data */
},
"old_record": {
/* same as record */
}
}

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);

PracticeDescription
Respond quicklyReturn 200 status immediately, process asynchronously if needed (30s timeout)
Handle duplicatesUse record IDs to ensure idempotent processing
Verify signaturesAlways verify webhook signatures using SDK utilities
Use HTTPSSecure webhook URLs with HTTPS in production
Log deliveriesKeep detailed logs of events for debugging
Monitor failuresUse 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
});

  • 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

// Test via SDK
await client.webhooks.test(webhookId, {
event: "INSERT",
table: "test_table",
record: { id: "test-id", name: "Test Record" }
});
// Or use dashboard: Webhooks → Select webhook → Test

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:

IssueSolution
Webhook not triggeringVerify table name, operations configured, webhook enabled
Delivery failuresCheck endpoint is public, responds within timeout, valid SSL
Signature failingUse correct secret, SDK’s verifyWebhookSignature(), raw body