Core
Webhooks
Event-driven integrations. Push Conductor events to any endpoint, or receive external events to trigger tool calls. All outgoing webhooks are signed with HMAC-SHA256.
Overview
Conductor supports two webhook directions:
Outgoing webhooks
Conductor pushes signed event payloads to your endpoints when tools are called, plugins change state, or the circuit breaker fires. Use these for observability, alerting, and automation — pipe Conductor events into Slack, PagerDuty, Datadog, or any custom endpoint.
Incoming webhooks
Register named endpoints that external services (GitHub, Stripe, CI pipelines) can POST to. Conductor validates, logs, and routes the payload to the specified handler tool. Requires HTTP transport.
Adding a webhook
Register an outgoing webhook endpoint with the CLI. Webhooks fire for every enabled event type unless you filter with --events.
# Subscribe to all events:
conductor webhooks add --url https://hooks.example.com/conductor
# Subscribe to specific events only:
conductor webhooks add \
--url https://hooks.example.com/conductor \
--events tool.called,tool.failed
# Add with HMAC signing secret:
conductor webhooks add \
--url https://hooks.example.com/conductor \
--events tool.called,tool.failed,circuit.opened \
--secret whsec_your_signing_secret
# List all registered webhooks:
conductor webhooks list
# Remove a webhook:
conductor webhooks remove <id>
# Send a test payload:
conductor webhooks test <id>Event types
8 event types are available. Expand each to see the full payload schema.
tool.calledFires when a tool execution completes successfully.{
"id": "evt_01HXYZ",
"type": "tool.called",
"timestamp": "2026-04-04T10:23:45.123Z",
"data": {
"tool_name": "github_issues",
"plugin": "github",
"input": { "owner": "myorg", "repo": "api", "state": "open" },
"output": { "count": 14, "issues": [...] },
"latency_ms": 312,
"session_id": "sess_abc123"
}
}tool.failedFires when a tool throws an error or times out.{
"id": "evt_01HXYZ",
"type": "tool.failed",
"timestamp": "2026-04-04T10:23:45.123Z",
"data": {
"tool_name": "slack_send",
"plugin": "slack",
"error": "ECONNREFUSED — Slack API unreachable",
"error_code": "PLUGIN_REQUEST_FAILED",
"circuit_state": "closed",
"attempt": 3
}
}plugin.readyFires when a plugin initializes successfully and its tools are registered.{
"id": "evt_01HXYZ",
"type": "plugin.ready",
"timestamp": "2026-04-04T10:23:45.123Z",
"data": {
"plugin_name": "github",
"tool_count": 20,
"zero_config": false,
"version": "1.0.0"
}
}plugin.errorFires when a plugin's initialize() throws or isConfigured() returns false.{
"id": "evt_01HXYZ",
"type": "plugin.error",
"timestamp": "2026-04-04T10:23:45.123Z",
"data": {
"plugin_name": "slack",
"error": "Missing SLACK_BOT_TOKEN in keychain",
"error_code": "PLUGIN_NOT_CONFIGURED",
"state": "not_configured"
}
}circuit.openedFires when a circuit breaker opens after repeated tool failures.{
"id": "evt_01HXYZ",
"type": "circuit.opened",
"timestamp": "2026-04-04T10:23:45.123Z",
"data": {
"tool_name": "github_issues",
"plugin": "github",
"failure_count": 5,
"threshold": 5,
"reset_after_ms": 30000
}
}circuit.closedFires when a circuit breaker resets and the tool is available again.{
"id": "evt_01HXYZ",
"type": "circuit.closed",
"timestamp": "2026-04-04T10:23:45.123Z",
"data": {
"tool_name": "github_issues",
"plugin": "github",
"downtime_ms": 32410
}
}audit.writtenFires every time an entry is written to the append-only audit log.{
"id": "evt_01HXYZ",
"type": "audit.written",
"timestamp": "2026-04-04T10:23:45.123Z",
"data": {
"seq": 1043,
"entry_hash": "sha256:a3f4...",
"previous_hash": "sha256:9b2c...",
"tool": "github_create_issue",
"chain_intact": true
}
}server.startedFires once when the MCP server finishes initialization.{
"id": "evt_01HXYZ",
"type": "server.started",
"timestamp": "2026-04-04T10:23:45.123Z",
"data": {
"version": "2.1.0",
"tool_count": 87,
"plugin_count": 12,
"transport": "stdio",
"zero_config_plugins": 8
}
}Signature verification
Every outgoing webhook request includes an X-Conductor-Signature header in the format sha256=<hex_digest>. Always verify this header before processing a payload. Use your signing secret from conductor webhooks add --secret.
const crypto = require("crypto");
function verifySignature(rawBody, signature, secret) {
// Conductor sends: X-Conductor-Signature: sha256=<hex>
const expected = "sha256=" + crypto
.createHmac("sha256", secret)
.update(rawBody) // rawBody must be the raw Buffer, not parsed JSON
.digest("hex");
// Use timingSafeEqual to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(signature, "utf8"),
Buffer.from(expected, "utf8")
);
}
// Express.js middleware example:
app.post("/conductor-events", express.raw({ type: "application/json" }), (req, res) => {
const sig = req.headers["x-conductor-signature"];
const secret = process.env.CONDUCTOR_WEBHOOK_SECRET;
if (!verifySignature(req.body, sig, secret)) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(req.body);
console.log("Event:", event.type, event.data);
res.status(200).send("ok");
});import hashlib
import hmac
def verify_signature(raw_body: bytes, signature: str, secret: str) -> bool:
"""
Verify X-Conductor-Signature header.
signature format: "sha256=<hex_digest>"
"""
expected = "sha256=" + hmac.new(
secret.encode("utf-8"),
raw_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
# FastAPI example:
from fastapi import FastAPI, Request, HTTPException, Header
app = FastAPI()
@app.post("/conductor-events")
async def handle_event(
request: Request,
x_conductor_signature: str = Header(...)
):
raw_body = await request.body()
secret = os.environ["CONDUCTOR_WEBHOOK_SECRET"]
if not verify_signature(raw_body, x_conductor_signature, secret):
raise HTTPException(status_code=401, detail="Invalid signature")
event = await request.json()
print(f"Event: {event['type']}", event['data'])
return {"ok": True}Important: raw body required
The HMAC is computed over the raw request body bytes. Do not parse the JSON before verifying — pass the raw Buffer (Node) or bytes (Python) to the HMAC function. Any whitespace difference will cause verification to fail.
Retry behavior
Conductor retries failed webhook deliveries (non-2xx response or connection error) up to 5 times with exponential backoff. After all retries are exhausted, the event is written to the dead letter queue at ~/.conductor/dlq.json for manual inspection and replay.
| Attempt | Delay | Notes |
|---|---|---|
| 1 | 1 second | Initial retry |
| 2 | 2 seconds | Exponential backoff |
| 3 | 4 seconds | Exponential backoff |
| 4 | 8 seconds | Exponential backoff |
| 5 | 16 seconds | Final attempt |
A small random jitter is added to each delay to prevent thundering herd issues when multiple webhooks are registered. Respond with any 2xx status code within 10 seconds to mark delivery as successful.
Incoming webhooks
Register a named endpoint on the Conductor HTTP server. External services POST to it, and Conductor validates, logs, and routes the payload to a handler tool. Requires HTTP transport (conductor mcp start --transport http).
conductor webhooks create \
--name "github-events" \
--url /hooks/github \
--handler "process_github_event"The endpoint becomes available at POST http://localhost:3000/hooks/github. All incoming payloads are recorded in the audit log.
Common use cases
Alert on circuit breaker open
circuit.openedSubscribe to circuit.opened and forward to PagerDuty or Slack when a tool repeatedly fails. Automatically know when GitHub or Linear APIs are degraded.
Audit trail to external SIEM
audit.writtenSubscribe to audit.written to stream every tool call and its inputs/outputs to your SIEM (Splunk, Datadog Logs, CloudWatch) for compliance.
Real-time observability dashboard
tool.called, tool.failedSubscribe to tool.called and tool.failed to populate a custom metrics dashboard with tool call rates, latency, and error rates.
React to external events
incomingSet up an incoming webhook for GitHub push events. When code is pushed to main, automatically trigger a Linear status update or Slack notification via Conductor tools.
Request headers
Every outgoing webhook request includes these headers:
| Header | Value |
|---|---|
Content-Type | application/json |
X-Conductor-Signature | sha256=<hmac_hex> (if secret configured) |
X-Conductor-Event | tool.called (the event type) |
X-Conductor-Delivery | evt_01HXYZ (unique delivery ID) |
X-Conductor-Version | 2.1.0 (Conductor version) |
User-Agent | Conductor-Webhooks/2.1.0 |