Webhook Adapter
The recommended adapter for production apps. Connects to any HTTP agent endpoint: LangChain, CrewAI, n8n, Flowise, or a plain Express/FastAPI proxy. Your API keys stay on the server, never in the mobile bundle.
Production pattern
In production: mobile app → Webhook adapter → your backend server → LLM API. Your server holds the API key, applies rate limits, and enforces auth.
Configuration
Ts
1const llmConfig: LocalLLMConfig = {
2 provider: "webhook",
3 baseUrl: "https://your-backend.com/api/chat",
4 model: "my-agent", // informational: sent in request body
5 apiKey: "optional-token", // optional Authorization Bearer header
6 timeoutMs: 60_000,
7};Request format
The adapter sends a POST with the full message history including the generated system prompt:
POST /api/chat
1{
2 "messages": [
3 { "role": "system", "content": "You are an AI assistant..." },
4 { "role": "user", "content": "Plan a trip to Tokyo" },
5 { "role": "assistant", "content": "{\"action\":\"render\",\"component\":\"TextInputCard\",...}" },
6 { "role": "user", "content": "7 days" }
7 ],
8 "model": "my-agent"
9}Response format
Any of these formats work: the adapter tries each field in order:
Json
1// Any of these response shapes are supported:
2{ "content": "{\"action\":\"render\",\"component\":\"ActionCard\",\"props\":{...}}" }
3{ "response": "..." }
4{ "output": "..." }
5{ "message": "..." }
6
7// Or a plain string body:
8{"action":"ask","message":"Which city would you like to visit?"}Server examples
Minimal Express server
server.ts
1import express from "express";
2import OpenAI from "openai";
3
4const app = express();
5app.use(express.json());
6
7const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
8
9app.post("/api/chat", async (req, res) => {
10 const { messages } = req.body;
11
12 const completion = await openai.chat.completions.create({
13 model: "gpt-4o-mini",
14 messages,
15 response_format: { type: "json_object" },
16 });
17
18 res.json({ content: completion.choices[0].message.content });
19});
20
21app.listen(3000);FastAPI server
server.py
from fastapi import FastAPIfrom openai import OpenAIfrom pydantic import BaseModelapp = FastAPI()client = OpenAI()class ChatRequest(BaseModel): messages: list model: str = "gpt-4o-mini"@app.post("/api/chat")async def chat(req: ChatRequest): response = client.chat.completions.create( model=req.model, messages=req.messages, response_format={"type": "json_object"}, ) return {"content": response.choices[0].message.content}With LangChain
langchain-server.ts
1import { ChatOpenAI } from "@langchain/openai";
2import { HumanMessage, SystemMessage, AIMessage } from "@langchain/core/messages";
3
4app.post("/api/chat", async (req, res) => {
5 const { messages } = req.body;
6
7 const llm = new ChatOpenAI({ model: "gpt-4o-mini" });
8
9 const langchainMessages = messages.map((m) => {
10 if (m.role === "system") return new SystemMessage(m.content);
11 if (m.role === "user") return new HumanMessage(m.content);
12 return new AIMessage(m.content);
13 });
14
15 const result = await llm.invoke(langchainMessages);
16 res.json({ content: result.content });
17});Authentication
If you set apiKey in the config, the adapter sends an Authorization: Bearer <apiKey> header. On your server, validate it before forwarding to the LLM:
Ts
1app.post("/api/chat", (req, res, next) => {
2 const token = req.headers.authorization?.replace("Bearer ", "");
3 if (token !== process.env.APP_SECRET) return res.status(401).json({ error: "Unauthorized" });
4 next();
5}, chatHandler);