Host Integration

Mount the Hitl internal API on your HTTP framework so workflows can POST to /.well-known/hitl/v1.

new Hitl() exposes three mount styles:

MethodUse when
hitl.routeHandlers.POSTNext.js App Router, frameworks with Request/Response route exports
hitl.fetch(req)Edge runtimes, custom routers, manual fetch handlers
hitl.handler(req, res)Node IncomingMessage / ServerResponse (Express, raw http)

Also mount Chat SDK webhooks separately when using chat adapters.

Next.js

App Router catch-all under .well-known:

app/.well-known/hitl/v1/[[...path]]/route.ts
import { hitl } from "@/lib/hitl";

export const { POST } = hitl.routeHandlers;

Express

import express from "express";
import { hitl } from "./lib/hitl";

const app = express();

app.post("/.well-known/hitl/v1/*", (req, res) => {
  void hitl.handler(req, res);
});

Or adapt hitl.fetch if you prefer middleware that builds a Request:

app.post("/.well-known/hitl/v1/*", async (req, res) => {
  const url = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
  const body = req.method !== "GET" ? JSON.stringify(req.body) : undefined;
  const response = await hitl.fetch(
    new Request(url, { method: req.method, headers: req.headers as HeadersInit, body }),
  );
  res.status(response.status);
  response.headers.forEach((v, k) => res.setHeader(k, v));
  res.send(await response.text());
});

Hono

import { Hono } from "hono";
import { hitl } from "./lib/hitl";

const app = new Hono();

app.post("/.well-known/hitl/v1/*", (c) => hitl.fetch(c.req.raw));

Fastify

import Fastify from "fastify";
import { hitl } from "./lib/hitl";

const app = Fastify();

app.post("/.well-known/hitl/v1/*", async (req, reply) => {
  const url = `${req.protocol}://${req.hostname}${req.url}`;
  const response = await hitl.fetch(
    new Request(url, { method: "POST", headers: req.headers as HeadersInit, body: JSON.stringify(req.body) }),
  );
  reply.status(response.status);
  for (const [k, v] of response.headers) reply.header(k, v);
  reply.send(await response.text());
});

NestJS

import { Controller, Post, Req, Res } from "@nestjs/common";
import type { Request, Response } from "express";
import { hitl } from "./hitl";

@Controller(".well-known/hitl/v1")
export class HitlController {
  @Post("*")
  handle(@Req() req: Request, @Res() res: Response) {
    return hitl.handler(req, res);
  }
}

Nitro

server/routes/.well-known/hitl/v1/[...path].ts
import { hitl } from "../../lib/hitl";

export default defineEventHandler((event) => hitl.fetch(toWebRequest(event)));

Nuxt

Use a Nitro server route (same as Nitro above) under server/routes/.well-known/hitl/v1/[...path].ts.

Astro

src/pages/.well-known/hitl/v1/[...path].ts
import type { APIRoute } from "astro";
import { hitl } from "../../../lib/hitl";

export const POST: APIRoute = ({ request }) => hitl.fetch(request);

SvelteKit

// src/routes/.well-known/hitl/v1/[...path]/+server.ts
import { hitl } from "$lib/hitl";
import type { RequestHandler } from "./$types";

export const POST: RequestHandler = ({ request }) => hitl.fetch(request);

Vite / raw Node

For Vite SSR or a standalone Node server:

import { createServer } from "node:http";
import { hitl } from "./lib/hitl";

createServer((req, res) => {
  if (req.method === "POST" && req.url?.startsWith("/.well-known/hitl/v1")) {
    void hitl.handler(req, res);
    return;
  }
  res.statusCode = 404;
  res.end();
}).listen(3000);

Security

Set HITL_SECRET (or pass secret to new Hitl()) in production. Workflows must send Authorization: Bearer <secret> on internal API calls when configured.