Webhook - ChatCLI
Skip to main content
The generic webhook is the channel for everything else: an internal chat, a ticketing system, a CI job or a script. Your system posts a JSON message to the gateway, and the gateway posts the replies back to a callback URL you choose. Both directions are signed with the same shared secret: an HMAC-SHA256 over a timestamp and the raw body, not the secret itself in a header.

How it works

The request is answered 202 before any work starts: attachment downloads and the agent run happen afterwards. Everything the agent produces arrives at your callback URL, and the kind field says what each post is.
Every request and callback is signed. The old X-ChatCLI-Secret header is no longer accepted; send two headers instead:
  • X-ChatCLI-Timestamp: <unix seconds> — must be within ±5 minutes of the gateway’s clock.
  • X-ChatCLI-Signature: sha256=<hex> — the hex HMAC-SHA256 of the exact string <timestamp>.<raw body>, keyed with CHATCLI_WEBHOOK_SECRET.
The gateway signs its callbacks to you the same way, over the body it sends, and re-signs on every retry. The entry secret is never echoed back in a callback. See the security hardening upgrade.

Setup

1

Configure ChatCLI

Generate the secret with something like openssl rand -hex 32, and put the same lines in your .env.
2

Start the gateway

For a service manager or a container, chatcli gateway runs the same daemon in the foreground.
3

Run a callback receiver

Any endpoint that accepts a JSON POST will do. There is a ready-made minimal receiver below.
4

Send a message

The gateway answers 202 Accepted right away; the notice, the progress and the reply arrive at your callback URL.

Environment variables

The shared settings (voice transcription, media limits, Conversation Hub) are on the Chat gateway page.

The request

POST to CHATCLI_WEBHOOK_PATH with the signature headers (the X-ChatCLI-Timestamp and X-ChatCLI-Signature described above) and a JSON body:

Responses

Every refusal names its reason in a JSON body, for example {"error": "missing_chat_id"}: Messages with the same chat_id reach the agent in the order they were accepted, even when one of them waits on an attachment download; different conversations do not wait on each other.

The callbacks

For each message the gateway posts JSON to CHATCLI_WEBHOOK_CALLBACK_URL:
When the reply carries an image, the body also has image_b64, plus image_mime and image_filename when they are known. Every callback carries these headers:

Delivery and retries

Any 2xx answer counts as delivered. A final or proactive post is tried again when your endpoint fails in a transient way: a network error, 408, 429 or 5xx, waiting 1 s and then 3 s. Any other 4xx is taken as your endpoint’s answer and is not repeated. The notice and the progress updates get a single attempt, so a slow receiver never holds the agent back.
An attempt can reach your endpoint and still fail on the way back, so keep the X-ChatCLI-Delivery-ID values you have processed and skip repeats.

Minimal receiver

Conversations and identity

The chat_id says where the reply goes. Which conversation the agent continues is decided by the Conversation Hub, from the sender:
Every sender shares one conversation, the same one as ChatCLI in your terminal and the other channels. A question asked in ticket-42 shows up as context in a message to ticket-99.That is what you want when the webhook is just another way for you to talk to your agent.
If several people talk to the agent through this channel, as in a ticketing system, set CHATCLI_HUB_ISOLATE=true and send each person’s user_id. By default everyone shares the same conversation.
To merge identities on purpose (you on the webhook and on Telegram, say), use the Conversation Hub bindings.

Attachments

  • audio_b64, image_b64 and document_b64 are decoded on the spot: invalid base64 is refused with 400. Inline base64 is unaffected by the URL rules below.
  • audio_url, image_url and document_url are downloaded after the 202, by the gateway itself, up to the CHATCLI_GATEWAY_MAX_AUDIO_BYTES and CHATCLI_GATEWAY_MAX_IMAGE_BYTES caps (20 MB each by default) and CHATCLI_GATEWAY_MAX_DOCUMENT_BYTES (50 MB). An attachment that cannot be downloaded is reported in the reply.
  • A URL attachment must be https, and the gateway refuses any address that resolves to a private, loopback, link-local or cloud-metadata target — so a caller holding the secret cannot use the gateway to probe its own network.
  • If a download fails, the message still runs and the agent is told, so the reply says the attachment did not arrive instead of answering as if nothing had been sent.

Proactive messages

With @send, the agent posts to your callback on its own, with kind: "proactive":
to: "webhook" uses CHATCLI_WEBHOOK_HOME_CHANNEL; webhook:<chat_id> addresses any conversation, and everything after the first : becomes the chat_id. Without CHATCLI_WEBHOOK_CALLBACK_URL, the send fails naming what is missing. Details in Proactive messages.

Security

Each message runs the agent with its tools and the shell, without confirmations. Treat the secret as a credential to the machine, and harden it with safety mode.
  • Use a long, random secret: it keys the signature in both directions, and the gateway refuses to start without CHATCLI_WEBHOOK_SECRET.
  • Verify the signature in your receiver — recompute sha256=HMAC(secret, "<timestamp>.<raw body>") with a constant-time comparison, and reject a timestamp outside ±5 minutes — before trusting the body. The secret is never sent in a header.
  • Serve the endpoint and the callback over HTTPS; CHATCLI_WEBHOOK_CALLBACK_URL must be https unless its host is loopback.
  • audio_url and image_url are fetched by the gateway itself, so they are restricted to https and blocked from private, loopback, link-local and metadata addresses.

Limits

  • A reply is cut at 3500 characters and ends with ….
  • The request body fits the largest audio, image and PDF accepted, all base64-encoded, plus 1 MB: about 121 MB with the default caps. Past that, 413.
  • Up to 32 messages waiting per conversation; past that, 429.
  • Only text and images come back in the callback; voice replies are not sent on this channel.

See also

Chat gateway

How messages run, voice, images, continuity and the other channels.

Conversation Hub

The shared conversation, per-sender isolation and bindings.

Proactive messages

Let the agent post to your callback on its own with @send.

Safety mode

Limit what an agent without confirmations can do.