Webhooks
Receive signed HTTP callbacks when policies change, resources are registered, or access anomalies are detected.
Edit this page on GitHubPermix can push signed event notifications to any HTTPS endpoint you register. Webhooks are useful for keeping external systems — audit logs, dashboards, SIEM tools — in sync with policy and access changes.
Supported event types#
| Event | Triggered when |
|---|---|
resource.registered | A resource is created or updated via POST /api/v1/resources |
policy.rbac.changed | A Casbin RBAC rule is created, updated, or deleted |
policy.abac.created | An ABAC policy is created |
policy.abac.updated | An ABAC policy is updated or toggled |
policy.abac.deleted | An ABAC policy is soft-deleted |
access.denied | A check request returns decision: deny |
service_api_key.created | A new service API key is issued |
service_api_key.revoked | A service API key is deleted |
Registering a webhook endpoint#
curl -X POST https://api.permix.dev/api/v1/webhooks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-service.example.com/webhooks/authz",
"events": ["policy.abac.created", "policy.abac.updated", "access.denied"],
"secret": "your-signing-secret"
}'Response 201:
{
"id": "wh_abc123",
"url": "https://your-service.example.com/webhooks/authz",
"events": ["policy.abac.created", "policy.abac.updated", "access.denied"],
"tenant_id": "tenant_prod",
"created_at": "2026-05-10T08:00:00Z"
}Event payload format#
Each delivery is a POST request with Content-Type: application/json:
{
"id": "evt_xyz789",
"type": "policy.abac.created",
"tenant_id": "tenant_prod",
"timestamp": "2026-05-10T09:15:00Z",
"data": {
"policy_id": "3f7a1b2c-...",
"resource": "invoice:read",
"effect": "allow",
"created_by": "user_123"
}
}Verifying signatures#
Every delivery includes an X-Authz-Signature header containing an HMAC-SHA256 of the raw request body, signed with your secret:
import crypto from "node:crypto";
export function verifyWebhook(
rawBody: string,
signature: string,
secret: string
): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}Always verify the signature before processing the event. Reject requests with an invalid or missing signature with 400 Bad Request.
Delivery behavior#
- At-least-once — events may be delivered more than once. Use
idfor idempotent processing. - Retry policy — failed deliveries (non-2xx or timeout) are retried with exponential backoff: 10s, 30s, 2m, 10m, 1h. After five failures the delivery is marked dead and dropped.
- Timeout — each delivery attempt times out after 10 seconds.
- Ordering — events within the same tenant are delivered in the order they were generated, but delivery retries may cause out-of-order arrival. Use
timestampto reorder if needed.
Managing webhooks#
# List registered webhooks
GET /api/v1/webhooks
# Update events or URL
PUT /api/v1/webhooks/{id}
# Delete a webhook
DELETE /api/v1/webhooks/{id}Testing your endpoint#
Use the test delivery endpoint to send a sample ping event to your registered URL:
curl -X POST https://api.permix.dev/api/v1/webhooks/{id}/test \
-H "Authorization: Bearer $TOKEN"Your endpoint should respond with 200 OK. The test event has "type": "webhook.test" and a synthetic payload.
Webhook delivery logs
Delivery attempts — including HTTP status, response body, and retry count — are visible in the webhook detail response at GET /api/v1/webhooks/{id}/deliveries. Useful for debugging endpoint issues without checking your own service logs.