# HTTP Bot Adapter — Integration Guide Integrate **any backend system** with a LangBot pipeline over plain HTTP. Push messages in via a signed webhook; receive replies on a callback URL. No long-lived connection, full support for message **aggregation** (many inbound messages merged into one turn) and **multi-part replies** (one turn → many outbound messages). This is the right adapter for **server-to-server** integrations — ticketing systems, CRMs, internal tools, custom web backends. (For an in-browser, real-time chat widget, use the embeddable Web Page Bot instead.) > **5-minute goal:** stand up a callback receiver, send a message, and watch a > multi-part reply arrive — using the reference client in > [`examples/http-bot/`](../../examples/http-bot/). --- ## 1. Mental model ``` Your backend ──(1) POST signed message──► LangBot /bots/ (pipeline runs: aggregate → think → reply) Your callback ◄─(2) POST signed reply(s)── LangBot one POST per reply part ``` - **(1) Inbound** is *fire-and-collect*: LangBot answers `202 Accepted` immediately and does **not** return the pipeline result on that response. - **(2) Outbound** replies arrive later as separate signed POSTs to your `callback_url`. A single turn may produce **several** callbacks (e.g. a tool call narration followed by the final answer). - Everything is keyed by a **`session_id` you choose** (e.g. a ticket number). Each `session_id` maps to one isolated LangBot conversation. --- ## 2. Create the bot 1. In the LangBot dashboard, add a bot and choose the **HTTP Bot** platform. 2. Fill in the config: | Field | Required | Notes | |---|---|---| | **Inbound Signing Secret** | yes | Your backend signs inbound requests with this. | | **Outbound Callback URL** | yes | Where LangBot POSTs replies. **Config-only** — cannot be overridden per message (SSRF protection). | | **Outbound Signing Secret** | no | LangBot signs callbacks with this; defaults to the inbound secret. | | **Default Session Type** | no | `person` (default) or `group`. | | **Require Inbound Signature** | no | Keep `true` in production. | | **Callback Timeout / Max Retries** | no | Defaults: 15s, 3 retries. | 3. Bind the bot to a **pipeline** and **enable** it. 4. Copy the **Inbound Webhook URL** shown in the config — it looks like `https://your-langbot/bots/`. --- ## 3. The signature scheme Both directions use the same dependency-free HMAC-SHA256 scheme: ``` signing_string = "{timestamp}." + raw_body_bytes signature = "sha256=" + hex(HMAC_SHA256(secret, signing_string)) ``` Sent as headers: | Header | Meaning | |---|---| | `X-LB-Timestamp` | Unix seconds. Rejected if more than **±300s** from server time. | | `X-LB-Signature` | `sha256=` over `"{timestamp}." + body`. | | `X-LB-Idempotency-Key` | *(optional, inbound)* dedup key; retries with the same key return `409`. | Verify outbound callbacks the same way, using the **outbound** secret (or the inbound secret if you left it blank). A six-line reference implementation is in `examples/http-bot/client.py` (`sign()` / `verify()`); a Node/TS version is in `client.ts`. --- ## 4. Send your first message (curl) ```bash BOT="https://your-langbot/bots/" SECRET="your-inbound-secret" BODY='{"session_id":"ticket-10293","message":[{"type":"Plain","text":"Export keeps failing on the dashboard."}]}' TS=$(date +%s) SIG="sha256=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -r | cut -d' ' -f1)" curl -sS -X POST "$BOT" \ -H "Content-Type: application/json" \ -H "X-LB-Timestamp: $TS" \ -H "X-LB-Signature: $SIG" \ -d "$BODY" # -> 202 {"code":0,"msg":"accepted","data":{"session_id":"ticket-10293","accepted_message_id":"in_...","aggregating":true}} ``` The reply(s) will be POSTed to your configured callback URL shortly after. --- ## 5. Inbound request format `POST /bots/{bot_uuid}` ```jsonc { "session_id": "ticket-10293", // REQUIRED. Your stable id. Maps 1:1 to a LangBot session. "session_type": "person", // optional: "person" | "group"; default from config "sender": { // optional metadata, surfaced to the pipeline/plugins "id": "user-5567", "name": "Alice" }, "message": [ // REQUIRED. A LangBot MessageChain (array of segments). { "type": "Plain", "text": "Export keeps failing on the dashboard." }, { "type": "Image", "url": "https://example.com/screenshot.png" } ] } ``` **Message segments.** Text uses `{"type":"Plain","text":"..."}`. Images use `{"type":"Image","url":"..."}` (or `base64`). Other supported types: `Voice`, `File`, `At`, `Quote`. > Note: the callback URL is **not** accepted in the body — it is taken only from > bot config. This is deliberate (prevents an attacker who obtains the inbound > secret from redirecting replies to an arbitrary host). ### Aggregation (N → 1) If your pipeline has **message aggregation** enabled, send several messages with the **same `session_id`** within the aggregation window and they are merged into **one** pipeline turn. No special flag — just reuse the `session_id`. --- ## 6. Outbound callback format LangBot POSTs each reply part to your `callback_url`: ```jsonc { "session_id": "ticket-10293", // echoes the inbound session "reply_to": "in_01H...", // the accepted_message_id this answers "sequence": 1, // 1-based ordinal within this turn "is_final": false, // true on the last part of the turn "stream": false, // true for streamed chunks "message": [ { "type": "Plain", "text": "Looking into it…" } ], "timestamp": "2026-06-22T09:00:01Z" } ``` Your endpoint should return `2xx` quickly. Non-2xx / timeout → LangBot retries with exponential backoff (up to `callback_max_retries`). ### Multi-part replies (1 → M) One turn may emit multiple callbacks, delivered **in `sequence` order** for a given session: ``` seq=1 is_final=false "Checking your export logs…" seq=2 is_final=false "Found 2 failed exports." seq=3 is_final=true "Fixed — please try again." ``` Stitch by `session_id` + `sequence`; the turn is complete when `is_final: true` arrives. --- ## 7. Reset a session Start a fresh conversation for a `session_id` (drops history): ``` POST /bots/{bot_uuid}/reset { "session_id": "ticket-10293", "session_type": "person" } → 200 { "code":0, "msg":"reset", "data": { "session_id":"ticket-10293", "removed": true } } ``` Signed exactly like an inbound message. --- ## 8. Synchronous convenience mode If you don't need streaming/multi-part and just want one reply back on the same HTTP call, POST to `/sync`. LangBot waits for the turn to finish and returns all parts **collapsed** into one array: ``` POST /bots/{bot_uuid}/sync { "session_id": "ticket-10293", "message": [ { "type":"Plain", "text":"hi" } ] } → 200 { "code":0, "msg":"ok", "data": { "session_id":"ticket-10293", "reply_to":"in_...", "message": [ {"type":"Plain","text":"..."}, ... ] } } ``` This is **lossy** (you lose `sequence` / streaming boundaries) and blocks up to `callback_timeout × 4` seconds. Prefer the callback model for anything real-time or multi-part. Only one in-flight `/sync` per `session_id`. Collection waits for both a final reply and the inbound listener to finish. An Agent's text reply does not end collection while that Agent is still exporting and sending output files. The same timeout bounds the entire wait; timing out does not cancel the running Agent. Replies sent after timeout use normal callback delivery. --- ## 9. Error envelope ```jsonc { "code": 40101, "msg": "invalid signature: signature_mismatch", "data": null } ``` | HTTP | code | meaning | |---|---|---| | 202 | 0 | accepted | | 400 | 40001 | malformed body / missing `session_id` or `message` | | 401 | 40101 | bad/expired signature | | 409 | 40901 | duplicate idempotency key | | 413 | 41301 | message too large (>1 MiB) | | 500 | 50001 | internal error | --- ## 10. Try it end-to-end in 5 minutes ```bash cd examples/http-bot pip install flask requests # Terminal 1 — your callback receiver (point the bot's callback_url here, e.g. via a tunnel): python client.py serve --port 8900 --secret SHARED_SECRET # Terminal 2 — push a message: python client.py push \ --url https://your-langbot/bots/ \ --secret SHARED_SECRET \ --session ticket-1 \ --text "hello" ``` Watch Terminal 1 print each reply part (`[part ]` / `[FINAL]`) with its sequence number — that's 1→M working, signatures verified. A machine-readable contract is in [`docs/http-bot-openapi.json`](../http-bot-openapi.json). --- ## 11. Security checklist - Keep **Require Inbound Signature** on in production. - Use **HTTPS** callback URLs; the URL is config-only (no per-message override). - Treat the secrets like passwords; rotate via the dashboard. - The inbound route is unauthenticated at the framework level **by design** — security comes entirely from the HMAC signature, so never disable it on a public deployment.