Outbound webhooks
Where the inbound webhook channel receives external events to process, outbound webhooks do the reverse: they notify your systems when a notable event occurs in betool.
Why
You probably already have:
- An alerting system (PagerDuty, Opsgenie, Slack).
- An ITSM tool (Jira, ServiceNow) to manage tickets.
- A custom dashboard that aggregates your operational status.
Rather than asking these systems to poll the betool API, subscribe them to outbound webhooks: they are notified instantly when something happens.
Configuration
- Administration → Outbound webhooks → New subscription.
- Choose:
- Target URL — the endpoint that will receive the POSTs.
- Events to listen to — see the list below.
- HMAC secret — auto-generated; use it on your receiver side to verify signatures.
- (Optional) Filters — restrict to executions of a specific pipeline, to a severity level, etc.
Available events
| Event | When it fires |
|---|---|
execution.failed | A pipeline execution has failed |
execution.requires_human | A confirmation node is awaiting validation |
execution.cost_threshold | An execution has exceeded a cost threshold |
billing.low_balance | The credit balance has dropped below the configured threshold |
billing.out_of_credits | The balance is zero (pre-call refusals are active) |
audit.cross_tenant_read | An external agent has read content from this organisation |
webhook.delivery_failed | A previous outbound webhook has failed 3 times |
POST format
POST /your/endpoint HTTP/1.1
Content-Type: application/json
X-Betool-Event: execution.failed
X-Betool-Signature: sha256=...
X-Betool-Delivery: dlv_01HXYZ...
{
"event": "execution.failed",
"delivered_at": "2026-05-24T10:42:13Z",
"org_id": "org_...",
"data": {
"execution_id": "exec_...",
"pipeline_id": "pip_...",
"pipeline_name": "support email triage",
"failed_node": "agent: classifier",
"error_kind": "llm_timeout",
"error_message": "Provider responded after 30s timeout"
}
}
HMAC signature
The X-Betool-Signature header contains sha256=<hmac> where hmac = HMAC-SHA256(secret, body). Verify it on receipt to ensure the request genuinely originates from betool:
import hmac, hashlib
def verify(body: bytes, signature: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(),
body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature)
Retry & idempotency
- If your endpoint responds 2xx, the event is marked as delivered.
- If non-2xx or timeout, betool retries with exponential backoff (1 min, 5 min, 30 min, 2 h, 12 h, 24 h — 6 attempts maximum).
- After 6 failures, the event is marked dead-letter and a
webhook.delivery_failedis emitted (to your other active subscriptions).
Each POST carries a unique X-Betool-Delivery identifier. If your endpoint receives the same delivery twice (network edge case), treat it as idempotent.
Security
- HTTPS required — plain HTTP subscriptions are refused.
- Rotatable secret — you can regenerate the secret at any time; in-flight POSTs using the old secret will still be accepted until expiry (5 min).
- Source IPs — betool publishes its outbound IPs on status.betool.fr so you can allow-list them at your firewall.