Skip to main content

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

EventFired when
session.startedAn agent session begins.
session.completedAn agent session transitions to stopped, error, or expired.
budget.exceededA budget alert threshold is crossed.
permission.requestedAn 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.

HeaderValue
Content-Typeapplication/json
User-AgentStyrby-Webhook/1.0
X-Styrby-Signaturesha256=<hex HMAC of raw body>
X-Styrby-EventEvent type (e.g. session.completed)
X-Styrby-Delivery-IdUUID of this delivery attempt; use for idempotency
X-Styrby-TimestampUnix 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.