Skip to content

Webhooks

Register webhooks to receive HTTP callbacks when tenants, config, or permissions change. Deliveries include HMAC-SHA256 signatures and automatic retry with exponential backoff.

Supported Events

EventTrigger
tenant.createdNew tenant created
tenant.updatedTenant properties changed
tenant.deletedTenant archived
tenant.movedTenant moved in hierarchy
config.updatedConfig key set or overridden
config.deletedConfig key removed
permission.createdPermission policy created
permission.updatedPermission policy updated
permission.deletedPermission policy deleted

Creating Webhooks

const webhook = await stratum.createWebhook({
url: "https://your-app.com/webhooks/stratum",
tenant_id: tenantId, // null for global webhooks
events: ["tenant.created", "config.updated"],
secret: "your-signing-secret",
});

Via the API:

Terminal window
curl -X POST http://localhost:3001/api/v1/webhooks \
-H "X-API-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/stratum",
"tenant_id": "TENANT_UUID",
"events": ["tenant.created", "config.updated"],
"secret": "your-signing-secret"
}'

The secret is encrypted at rest using AES-256-GCM. It is used to generate HMAC-SHA256 signatures for each delivery.

Delivery Format

Each webhook delivery is an HTTP POST to your URL with these headers:

HeaderValue
Content-Typeapplication/json
X-Stratum-Signaturesha256=<hmac_hex>
X-Stratum-EventEvent type (e.g., tenant.created)
X-Stratum-DeliveryUnique delivery ID

The body contains the event payload:

{
"event": "tenant.created",
"timestamp": "2024-06-15T10:30:00.000Z",
"data": {
"id": "tenant-uuid",
"name": "Acme Corp",
"slug": "acme_corp",
"parent_id": null,
"isolation_strategy": "SHARED_RLS"
}
}

Verifying Signatures

Verify the HMAC-SHA256 signature to ensure the delivery is authentic:

import crypto from "crypto";
function verifyWebhook(body: string, signature: string, secret: string): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(`sha256=${expected}`),
Buffer.from(signature)
);
}
// In your webhook handler
app.post("/webhooks/stratum", (req, res) => {
const signature = req.headers["x-stratum-signature"];
if (!verifyWebhook(JSON.stringify(req.body), signature, WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
// Process the event
res.status(200).send("OK");
});

Retry Logic

Failed deliveries (non-2xx responses or network errors) are retried with exponential backoff. The retry schedule depends on your control plane configuration.

Testing Webhooks

Send a test event to verify your endpoint:

Terminal window
curl -X POST http://localhost:3001/api/v1/webhooks/WEBHOOK_ID/test \
-H "X-API-Key: YOUR_KEY"

This sends a synthetic test event to the webhook URL and returns the response code.

Delivery History

View delivery history for a specific webhook:

Terminal window
curl http://localhost:3001/api/v1/webhooks/WEBHOOK_ID/deliveries \
-H "X-API-Key: YOUR_KEY"

Dead-Letter Queue

Failed deliveries that exhaust their retry attempts land in the dead-letter queue (DLQ).

View Delivery Stats

Terminal window
curl http://localhost:3001/api/v1/webhooks/deliveries/stats \
-H "X-API-Key: YOUR_KEY"

Response:

{
"total": 150,
"pending": 5,
"success": 130,
"failed": 15
}

List Failed Deliveries

Terminal window
curl "http://localhost:3001/api/v1/webhooks/deliveries/failed?limit=50" \
-H "X-API-Key: YOUR_KEY"

Retry Failed Deliveries

Retry a single failed delivery:

Terminal window
curl -X POST http://localhost:3001/api/v1/webhooks/deliveries/DELIVERY_ID/retry \
-H "X-API-Key: YOUR_KEY"

Retry all failed deliveries:

Terminal window
curl -X POST http://localhost:3001/api/v1/webhooks/deliveries/retry-all \
-H "X-API-Key: YOUR_KEY"

Managing Webhooks

Update

Terminal window
curl -X PATCH http://localhost:3001/api/v1/webhooks/WEBHOOK_ID \
-H "X-API-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"events": ["tenant.created", "tenant.updated", "config.updated"]}'

Delete

Terminal window
curl -X DELETE http://localhost:3001/api/v1/webhooks/WEBHOOK_ID \
-H "X-API-Key: YOUR_KEY"

SSRF Protection

Webhook URLs are validated to prevent Server-Side Request Forgery:

  • DNS resolution is checked to block private IP ranges
  • IPv4 and IPv6 private ranges are blocked (10.x, 172.16-31.x, 192.168.x, ::1, fc00::, etc.)
  • Cloud metadata endpoints are blocked (169.254.169.254, metadata.google.internal)
  • DNS rebinding attacks are mitigated by resolving the hostname at delivery time