Framework Integration
Pillar Cloud sends tool calls to your server via HTTP POST. The SDK provides built-in adapters for popular frameworks — each adapter reads the request body and headers, delegates to pillar.handle(), and sends back the response.
TypeScript frameworks
Express
import express from 'express';import { pillar } from './pillar-tools';const app = express();app.use(express.json());app.post('/pillar', pillar.expressHandler());app.listen(3000, () => {console.log('Pillar webhook listening on port 3000');});
Express must parse the raw JSON body. Make sure express.json() middleware is applied before the Pillar route.
Fastify
import Fastify from 'fastify';import { pillar } from './pillar-tools';const fastify = Fastify();fastify.post('/pillar', pillar.fastifyHandler());fastify.listen({ port: 3000 }, () => {console.log('Pillar webhook listening on port 3000');});
Hono
import { Hono } from 'hono';import { pillar } from './pillar-tools';const app = new Hono();app.post('/pillar', pillar.honoHandler());export default app;
Next.js (App Router)
// app/api/pillar/route.tsimport { pillar } from '@/lib/pillar-tools';export const POST = pillar.nextHandler();
The nextHandler() adapter works with the standard Web Request / Response API used by Next.js App Router route handlers.
Standalone
If you don't use a framework, the SDK can start its own HTTP server:
import { Pillar, defineTool } from '@pillar-ai/server';import { z } from 'zod';const pillar = new Pillar({secret: process.env.PILLAR_SECRET!,endpointUrl: 'https://api.myapp.com/pillar',});const ping = defineTool({name: 'ping',description: 'Health check',input: z.object({}),execute: async () => ({ pong: true }),});await pillar.registerTools([ping]);pillar.serve({ port: 8787 });
This starts a plain Node.js HTTP server on the specified port. All POST requests are dispatched to pillar.handle().
Python frameworks
Django
# urls.pyfrom django.urls import pathfrom myapp.pillar_tools import pillarurlpatterns = [path("pillar/", pillar.django_view()),]
The Django adapter returns a CSRF-exempt async view. It reads request.body and the X-Pillar-Signature header automatically.
Flask
# app.pyfrom flask import Flaskfrom myapp.pillar_tools import pillarapp = Flask(__name__)app.route("/pillar", methods=["POST"])(pillar.flask_handler())
The Flask adapter wraps the async handle() method in asyncio.run() so it works with Flask's synchronous request handling.
FastAPI
# main.pyfrom fastapi import FastAPI, Requestfrom fastapi.responses import JSONResponsefrom myapp.pillar_tools import pillarapp = FastAPI()@app.post("/pillar")async def pillar_endpoint(request: Request):body = await request.body()headers = {"X-Pillar-Signature": request.headers.get("x-pillar-signature", "")}result, status_code = await pillar.handle(body, headers)return JSONResponse(result, status_code=status_code)
FastAPI doesn't have a built-in adapter method — call pillar.handle() directly in your route handler. This gives you full control over the request/response lifecycle.
Standalone
from pillar import Pillar, ToolContextpillar = Pillar(secret="plr_...")@pillar.tool(description="Health check")async def ping(ctx: ToolContext) -> dict:return {"pong": True}pillar.serve(port=8787)
This starts a blocking HTTP server using Python's standard library. All POST requests are dispatched to pillar.handle().
Custom integration
If your framework isn't listed, you can call pillar.handle() directly. It accepts a raw body (string or bytes) and a headers dict, and returns a tuple of [result, statusCode]:
// TypeScriptconst [result, statusCode] = await pillar.handle(rawBody, {'x-pillar-signature': signatureHeader,});// Send `result` as JSON with `statusCode`
# Pythonresult, status_code = await pillar.handle(raw_body, {"X-Pillar-Signature": signature_header,})# Return result as JSON with status_code
Next steps
- Webhook Security — How request signatures work
- Confirmation Flows — Ask users to confirm before destructive actions
- Testing — Write tests for your server tools