Webhooks
The webhook channel is a generic inbound/outbound HTTP adapter. It runs its own embedded HTTP server on a port you choose, accepts JSON-shaped messages, hands them to the agent, and (optionally) POSTs the agent’s replies to a URL you specify. Use it as the universal adapter for any system that can produce an HTTP POST.
Not the same as the gateway’s
/webhookendpoint. The gateway service has its ownPOST /webhookfor paired clients hitting the agent over HTTP — that lives under[gateway]and is described in Operations → Network deployment. This page documents the[channels.webhook]channel only.
配置
[channels.webhook]
enabled = true
port = 8090 # TCP port the channel binds (0.0.0.0:{port})
listen_path = "/webhook" # path the embedded server listens on; default "/webhook"
send_url = "https://example.com/callback" # optional outbound URL for agent replies
send_method = "POST" # "POST" (default) or "PUT"
auth_header = "Bearer s3cret" # optional Authorization header value for outbound requests
secret = "..." # optional shared secret for inbound HMAC-SHA256 verification
Full field reference: Config.
入站
The channel binds 0.0.0.0:{port} and routes POST {listen_path}.
Request body (JSON):
{
"sender": "alice",
"内容": "Hello, agent.",
"thread_id": "optional-conversation-id"
}
sender— required, used as the message’s sender identity.content— required, the user message handed to the agent. Empty content returns400.thread_id— optional. If set, the agent’s reply targets the same thread; otherwise replies targetsender.
Success returns 200 OK. Malformed JSON or empty content returns 400. Backpressure (channel queue full) returns 503.
签名验证
When secret is set, every inbound request must carry an X-Webhook-Signature header:
X-Webhook-Signature: sha256=<hex-encoded HMAC-SHA256 of the raw body>
The channel computes HMAC-SHA256(secret, raw_body), hex-encodes it, and compares against the header value (the sha256= prefix is stripped before decode). Mismatch or missing header returns 401.
When secret is unset, no verification runs — every request is accepted. Don’t expose an unsecured webhook channel to the public internet; either set secret, restrict access at a reverse proxy, or run the listener bound to a private network only.
出站
When send_url is set, every agent reply is delivered as an HTTP request to that URL:
{send_method} {send_url}
Authorization: {auth_header} # only if auth_header is set
Content-Type: application/json
{
"content": "agent reply text",
"thread_id": "optional thread id",
"recipient": "optional recipient id"
}
send_methodisPOST(default) orPUT. Any other value falls back toPOST.auth_headeris sent verbatim as theAuthorizationheader value — include the scheme yourself (e.g.Bearer xyz,Basic dXNlcjpwYXNz).recipientis omitted when empty.- Non-2xx responses raise an error in logs; the agent reply is considered failed.
When send_url is unset, agent replies are dropped silently (logged at debug). This is the right configuration for fire-and-forget inbound flows where the response is delivered through some other channel.
公开暴露
The channel binds to 0.0.0.0 directly. To expose it on the public internet:
- Reverse proxy — terminate TLS at nginx / Caddy / Traefik and proxy to the channel’s port. See Operations → Network deployment.
- Tunnel — configure
[tunnel](ngrok,cloudflare, ortailscale) and the daemon brings up the tunnel alongside the channel. - Local-only — run inside a private network and have your producer hit the LAN/loopback address directly.
Always pair public exposure with secret. An unauthenticated webhook listener is an open ingress to the agent.
Outbound sends
Webhook channels can also POST/PUT outbound messages to a configured send_url — used when the agent replies through the channel rather than only receiving inbound events. Outbound delivery is configured under the singular [channels.webhook] prefix (a separate schema surface from the inbound [channels.webhooks.<name>] blocks above; reconciling that shape difference in this page is tracked separately):
[channels.webhook]
send_url = "https://example.com/callback"
send_method = "POST" # or "PUT"; default: "POST"
auth_header = "Bearer ..." # optional Authorization header
# Retry tunables (all optional):
max_retries = 3 # default: 3; set to 0 to disable retries
retry_base_delay_ms = 500 # exponential-backoff base; default: 500
retry_max_delay_ms = 30000 # per-wait cap; default: 30000 (30s)
Outbound sends retry transient failures — network errors, HTTP 429, and HTTP 5xx — with exponential backoff (±25% jitter) capped by retry_max_delay_ms. Non-429 4xx responses fail immediately without retrying. When the server returns a Retry-After header on 429 or 503, that value is honored and also clamped by retry_max_delay_ms. Setting max_retries = 0 preserves the prior fire-and-forget behavior byte-for-byte.
代码
- Channel:
crates/zeroclaw-channels/src/webhook.rs - Config:
crates/zeroclaw-config/src/schema.rs(WebhookConfig)
另见
- Operations → Network deployment — TLS termination, tunnels, the gateway’s separate
/webhook - 通道 → 概述