Skip to content

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 FastAPI
from openai import OpenAI
from pydantic import BaseModel
app = 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);