Webhooks
A webhook sends your database changes to a URL you choose: your own server, or a service like Slack or a CRM. When a record in the collection is created, updated or deleted, Koolbase POSTs the event there, signed so the receiver can check it came from Koolbase, and retries if the receiver is down.
Webhooks or Functions?
Create a webhook
Manage webhooks from the CLI (v0.8.2 or later) or the API. Creating, listing and deleting them needs a project owner or admin, or an admin-scoped API key, because a webhook sends project data outside Koolbase.
curl -X POST https://api.koolbase.com/v1/projects/PROJECT_ID/webhooks \
-H "Authorization: Bearer YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/koolbase-webhook",
"collection": "orders",
"events": ["db.record.created", "db.record.updated"]
}'The response includes the webhook and its signing secret:
{
"webhook": { "id": "…", "url": "https://example.com/koolbase-webhook", "collection": "orders",
"events": ["db.record.created", "db.record.updated"], "enabled": true },
"secret": "whsec_…"
}The secret is shown once
secret straight away, for example as an environment variable on your server. Koolbase keeps it encrypted and never returns it again. If you lose it, delete the webhook and create a new one.db.record.createdA record was inserted into the collectiondb.record.updatedA record was updateddb.record.deletedA record was deletedFrom the CLI
The same, from a terminal. --event can be repeated or comma-separated, and insert, update and delete work as short names. The secret is printed once, when you create the webhook.
koolbase webhooks create --project PROJECT_ID \
--url https://example.com/koolbase-webhook --collection orders --event insert,update
koolbase webhooks list --project PROJECT_ID
koolbase webhooks deliveries WEBHOOK_ID --project PROJECT_ID
koolbase webhooks redeliver DELIVERY_ID --project PROJECT_ID
koolbase webhooks delete WEBHOOK_ID --project PROJECT_IDWhat your server receives
A POST with a JSON body. data is the same payload a Function trigger gets: record for created and updated, record_id for deleted.
{
"id": "evt_4c1b…",
"type": "db.record.created",
"collection": "orders",
"created_at": "2026-09-24T12:00:00Z",
"data": { "record": { "$id": "rec_123", "total": 42 } }
}X-Koolbase-Signaturet=<unix time>,v1=<signature>. Check it before trusting the body.X-Koolbase-EventThe event type, e.g. db.record.createdX-Koolbase-Event-IdThe event's id; the same on every retry. Use it to ignore repeats.X-Koolbase-DeliveryThis delivery's id, for the delivery logVerify the signature
v1 is a hex HMAC-SHA256 of "<t>.<raw body>" using your webhook secret. Check it against the raw request body, before parsing it, and reject requests more than five minutes old. These examples are tested against Koolbase's signing code.
import { createHmac, timingSafeEqual } from "node:crypto"
import { Buffer } from "node:buffer"
// Returns true only if the request really came from Koolbase and is recent.
export function verifyKoolbaseSignature(secret: string, header: string, rawBody: string, toleranceSeconds = 300): boolean {
const parts: Record<string, string> = {}
for (const piece of header.split(",")) {
const i = piece.indexOf("=")
if (i > 0) parts[piece.slice(0, i).trim()] = piece.slice(i + 1).trim()
}
const t = Number(parts.t)
if (!parts.v1 || !Number.isFinite(t)) return false
if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false
const expected = createHmac("sha256", secret).update(t + "." + rawBody).digest("hex")
const a = Buffer.from(expected)
const b = Buffer.from(parts.v1)
return a.length === b.length && timingSafeEqual(a, b)
}import hashlib
import hmac
import time
# Returns True only if the request really came from Koolbase and is recent.
def verify_koolbase_signature(secret: str, header: str, raw_body: bytes, tolerance_seconds: int = 300) -> bool:
parts = dict(p.strip().split("=", 1) for p in header.split(",") if "=" in p)
try:
t = int(parts["t"])
except (KeyError, ValueError):
return False
if abs(time.time() - t) > tolerance_seconds:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))Answer with any 2xx status once you have accepted the event, and do slow work afterwards: Koolbase waits at most 15 seconds for a response.
Retries and the delivery log
Any answer other than 2xx, or no answer, is retried after 1, 2, 4, 8 and 16 minutes: six attempts in all. After the last one the delivery is marked failed. Redirects are not followed. Every attempt carries the same event id, so a receiver that saw an event once can ignore it the next time.
# Recent deliveries for a webhook
curl https://api.koolbase.com/v1/projects/PROJECT_ID/webhooks/WEBHOOK_ID/deliveries \
-H "Authorization: Bearer YOUR_ADMIN_KEY"
# Send one again, from the start
curl -X POST https://api.koolbase.com/v1/projects/PROJECT_ID/webhooks/deliveries/DELIVERY_ID/redeliver \
-H "Authorization: Bearer YOUR_ADMIN_KEY"Each delivery shows its status (pending, delivered or failed), attempt, next_attempt_at, last_status_code and last_error. List webhooks with GET /v1/projects/PROJECT_ID/webhooks and remove one with DELETE /v1/projects/PROJECT_ID/webhooks/WEBHOOK_ID; its deliveries go with it.
Rules
- The URL must use
https, on port 443 or 8443. - It must be a public address. Local, private and cloud-metadata addresses are refused, both when you create the webhook and again at the moment of every delivery, whatever the hostname resolves to.
- A webhook watches one collection. Create more than one to cover several collections.