Webhooks & event subscriptions
A Webhook is an HTTPS POST AxisSynapse sends to your endpoint every time something interesting happens in your workspace — a SAML provider toggled, a passkey enrolled, an erasure approved, a payroll transmitted. Every payload is HMAC signature-signed with your subscription's shared secret so your endpoint can verify the event came from us and was not modified in transit. Failed deliveries retry with exponential backoff; persistent failures trip a Circuit breaker that pauses delivery until you re-enable.
TL;DR — Add a subscription in Settings → Developer → Webhooks. Paste your endpoint URL, pick the event categories you care about, copy the signing secret, click Test delivery. Once verified, flip Enabled on; failed attempts retry automatically.
Before you start
- You need a public HTTPS endpoint that returns 200 within a few seconds. HTTP is rejected — TLS is required for the HMAC posture to be meaningful.
- Decide which event categories matter. The canonical catalogue lives at Reference → Webhook events.
- Have a secret store ready. The signing secret is shown once; store it in a vault, not in source control.
The HMAC signature format
Every outbound delivery carries an X-Axissynapse-Signature
header of the form v1=hex(HMAC_SHA256(secret, body)).
This is the Stripe-style signature scheme; libraries that verify
Stripe webhooks port to AxisSynapse with the header name changed.
Add a webhook subscription
Open Settings → Developer → Webhooks
The page lists existing subscriptions with their URL, enabled state, last-delivery timestamp, and circuit-breaker status.
Click "Add subscription"
A drawer slides in with the new-subscription form.
Paste your endpoint URL
Absolute HTTPS URL. AxisSynapse validates that the URL parses, that it's HTTPS, and that the host resolves.
Pick the event categories
Glob-style filters are supported. Tick categories from the catalogue, or type a custom glob (e.g.
platform.saml.*to subscribe to every SAML event).Copy the signing secret
The secret is shown once. Paste it into your endpoint's secret store immediately.
Click "Test delivery"
AxisSynapse sends a synthetic event to your endpoint. The drawer shows the response code, body preview, and latency. If your endpoint returns 200, you're verified.
Toggle "Enabled" on
Future events matching your filters are dispatched as they fire. The subscription's row updates with delivery counters and a health badge.
The payload shape
Every delivery carries the same envelope. The data payload is
event-specific; see Reference → Webhook events
for the shape per event type.
{
"id": "evt_2W6Yk…",
"type": "platform.saml.provider.enabled",
"created_at": "2026-06-02T14:32:11.483Z",
"tenant_id": "ws_a8…",
"data": { /* event-specific payload */ }
}
Verify the signature before parsing the body. A delivery that fails verification should be 401'd and logged — never quietly dropped.
import crypto from "node:crypto";
function verify(req, secret) {
const sig = req.headers["x-axissynapse-signature"];
const expected = "v1=" + crypto
.createHmac("sha256", secret)
.update(req.rawBody)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(sig),
Buffer.from(expected),
);
}
Retry behavior
When your endpoint returns a non-2xx code (or times out), AxisSynapse retries the delivery with exponential backoff and jitter. The specifics are tuned to balance pressure on your endpoint with freshness — the first retry lands seconds later; later retries spread over minutes.
If your endpoint stays unhealthy long enough that the subscription
accumulates several consecutive failures, the circuit breaker
trips and dispatch pauses. The audit log records
TENANT_WEBHOOK_CIRCUIT_TRIPPED; you re-enable from the
subscription's row after fixing the endpoint.
| Field | What it does | Accepted values / default |
|---|---|---|
| First retry | Catches a transient blip. | Seconds after the failure. |
| Later retries | Spread out so a slow endpoint isn't hammered. | Exponential backoff with jitter; the total retry window is bounded. |
| Circuit breaker | Pauses delivery after persistent failure. | Trips after a tightly capped run of consecutive failures. Re-enable manually after recovering the endpoint. |
| Replay | Re-deliver events from the deliveries log. | Available on the subscription's deliveries tab; useful after fixing a downstream bug. |
Filter syntax
The category picker speaks glob — segments separated by ., with
* matching one segment and ** matching any number.
| Field | What it does | Accepted values / default |
|---|---|---|
platform.saml.provider.enabled | Exact event match. | Useful when you only care about one event. |
platform.saml.* | Every SAML event. | Common for SIEM ingestion. |
platform.** | Every platform event. | Use sparingly — this is a firehose. |
account.mfa.* | Every per-account MFA event. | Common for anomaly-detection pipelines. |
account.security_alert.** | Derived security alerts (counter regression, replay, impossible-travel, etc.). | Recommended baseline for any security operations integration. |
Every field, explained
| Field | What it does | Accepted values / default |
|---|---|---|
| Endpoint URL | Where AxisSynapse POSTs deliveries. | Absolute HTTPS URL. The host must resolve at creation time. |
| Event categories | Which events match the subscription. | One or more glob-style filters. Empty = no events delivered. |
| Signing secret | Shared secret used to compute the HMAC signature. | Shown ONCE. Store in a vault. Rotate periodically; rotation is a step-up-protected action. |
| Description | Internal note explaining what the subscription is for. | Up to 200 characters. Shown in Security Console next to delivery alerts. |
| Enabled | Whether new events are dispatched. | Off by default; flip on after Test delivery passes. |
| Custom headers | Static headers added to every delivery (e.g. a tenant-id your endpoint uses for routing). | Up to 5 headers. No reserved names (Authorization, X-Axissynapse-*, etc.). |
| Test delivery | Send a synthetic event to verify the endpoint. | Returns response code + body preview + latency. Doesn't affect real delivery state. |
What appears in the audit log
TENANT_WEBHOOK_CREATED/..._UPDATED/..._DELETED— configuration lifecycle.TENANT_WEBHOOK_DISABLED— manual disable from the admin UI.TENANT_WEBHOOK_CIRCUIT_TRIPPED— the circuit breaker activated. Pair with the subscription's deliveries-tab failure log to diagnose.TENANT_WEBHOOK_TEST_SENT— every Test delivery click.
Successful and failed deliveries themselves do not emit per-row audit codes — that would balloon the audit volume. Use the deliveries tab for delivery-level data; the audit log records lifecycle and breaker events only.
Common gotchas
- "My endpoint is returning 200 but the signature check fails." You're computing HMAC over a parsed-then-re-stringified body instead of the raw bytes. Capture the raw request body before any parser touches it.
- "Test delivery succeeds but real events never arrive." Your
filter doesn't match the events you expect. Open the catalogue at
/reference/events, pick the exact event name, and switch the filter to that. - "The circuit tripped during my maintenance window." Add an HTTP 503 to your maintenance page — AxisSynapse counts 503 as a normal retryable failure (rather than a hard error) and the breaker rarely trips on a planned outage.
- "My downstream system caches deliveries and replayed them
later." Use the
idfield for deduplication; identicalidalways means identical event. - "I lost my signing secret." It can't be retrieved. Rotate from the subscription's row (a step-up-protected action) and update your endpoint.
Troubleshooting
| Error code | What it means | Fix |
|---|---|---|
| WEBHOOK_INVALID_URL | The endpoint isn't HTTPS or doesn't resolve. | Provide a valid HTTPS URL. |
| WEBHOOK_TEST_TIMEOUT | Your endpoint didn't respond within the test timeout. | Confirm your endpoint is reachable and fast. |
| WEBHOOK_BAD_RESPONSE_CODE | The endpoint returned non-2xx during a test. | Check the response body in the test result. |
| WEBHOOK_CIRCUIT_OPEN | The circuit breaker is open; delivery paused. | Recover your endpoint and re-enable from the subscription row. |
| WEBHOOK_FILTER_INVALID | A glob filter failed to parse. | Use only . separators, *, and **. |
Related