Webhooks
Get HTTP event callbacks when events happen in your Styrby account. Available on Pro (3 webhooks) and Power (10 webhooks) tiers. Not available on Free.
Available Events
| Event | Fired when |
|---|---|
| session.started | An agent session begins. |
| session.completed | An agent session transitions to stopped, error, or expired. |
| budget.exceeded | A budget alert threshold is crossed. |
| permission.requested | An agent requests a tool call that needs approval. |
Setting Up a Webhook
Go to Settings > Webhooks in the dashboard. Click "Add Endpoint" and provide:
- Name: A label for this webhook (max 100 characters).
- URL: Your HTTPS endpoint. Must return 2xx within 30 seconds. Internal IPs, localhost, link-local addresses, and cloud metadata services are blocked. DNS rebinding is mitigated by re-resolving the hostname before each delivery.
- Events: Select which events to subscribe to (at least one required).
After creation, the signing secret is shown once. Store it securely; it cannot be retrieved again. If lost, delete and recreate the webhook.
Payload Format
All webhook payloads are JSON. The shape of the data object varies by event type.
session.started
{
"event": "session.started",
"timestamp": "2026-03-22T14:30:00Z",
"data": {
"session_id": "ses_8f3k2m9x",
"agent_type": "claude",
"model": "claude-sonnet-4-20250514",
"project_path": "/home/user/my-project",
"machine_id": "mch_abc123",
"started_at": "2026-03-22T14:30:00Z"
}
}session.completed
{
"event": "session.completed",
"timestamp": "2026-03-22T14:45:12Z",
"data": {
"session_id": "ses_8f3k2m9x",
"agent_type": "claude",
"model": "claude-sonnet-4-20250514",
"status": "stopped",
"started_at": "2026-03-22T14:30:00Z",
"ended_at": "2026-03-22T14:45:12Z",
"total_cost_usd": 0.042,
"total_input_tokens": 12840,
"total_output_tokens": 3210,
"message_count": 24
}
}budget.exceeded
{
"event": "budget.exceeded",
"timestamp": "2026-03-22T14:45:12Z",
"data": {
"alert_id": "bgt_xyz789",
"alert_name": "Daily $10 limit",
"current_spend_usd": 10.23,
"threshold_usd": 10.00,
"period": "daily",
"action": "notify",
"percentage_used": 102.3
}
}permission.requested
{
"event": "permission.requested",
"timestamp": "2026-03-22T14:32:00Z",
"data": {
"session_id": "ses_8f3k2m9x",
"message_id": "msg_abc999",
"agent_type": "claude",
"model": "claude-sonnet-4-20250514",
"project_path": "/home/user/my-project",
"risk_level": "high",
"tool_name": "Bash",
"created_at": "2026-03-22T14:32:00Z"
}
}Request Headers
Every webhook delivery is a POST with the following headers. Use them to verify origin, deduplicate, and correlate with delivery logs.
| Header | Value |
|---|---|
| Content-Type | application/json |
| User-Agent | Styrby-Webhook/1.0 |
| X-Styrby-Signature | sha256=<hex HMAC of raw body> |
| X-Styrby-Event | Event type (e.g. session.completed) |
| X-Styrby-Delivery-Id | UUID of this delivery attempt; use for idempotency |
| X-Styrby-Timestamp | Unix seconds when the request was sent |
Signature Verification
The X-Styrby-Signature header carries an HMAC-SHA256 of the raw request body, prefixed withsha256=. Compute the same HMAC with your signing secret and compare with a constant-time check. Reject the request if the comparison fails.
import crypto from "crypto";
function verifyWebhook(
payload: string,
signatureHeader: string,
secret: string
): boolean {
// The header is formatted as "sha256=<hex>". Strip the prefix.
const signature = signatureHeader.replace("sha256=", "");
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// In your endpoint handler:
const isValid = verifyWebhook(
rawBody,
req.headers["x-styrby-signature"],
process.env.STYRBY_WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).json({ error: "Invalid signature" });
}Retry Policy
If your endpoint returns a non-2xx status or times out (30 seconds), Styrby retries with exponential backoff (3 total attempts):
- Attempt 1: immediate
- Retry 1: after 1 minute
- Retry 2: after 2 minutes (final attempt)
After 3 total failed attempts, the delivery is marked as failed. You can view delivery history and retry failed events from the webhook detail page in Settings > Webhooks.
Idempotency
Retries reuse the same X-Styrby-Delivery-Id. Manual retries from the dashboard issue a new delivery row with a new ID. Treat the delivery ID as the idempotency key on your end and store it for at least the retry window (3 minutes) to drop duplicates safely.