Runhook is a tiny webhook runtime that executes user‑defined TypeScript/JavaScript functions with Deno workers. It ships with a minimal REST API, an MCP SSE endpoint, and a SQLite backing store.
- Execute
run(ctx)on webhooks - Store functions in SQLite (with ULID ids)
- MCP SSE endpoint for tool-based management
- Per‑request worker isolation + timeout
- Optional admin token for management endpoints
- Deno 2.x
deno task devBy default it listens on http://0.0.0.0:8787.
The dev task enables --unstable-worker-options so per‑worker permissions work.
All /functions* endpoints require admin auth when RUNHOOK_ADMIN_TOKEN is set.
Create a function:
curl -X POST http://localhost:8787/functions \
-H "content-type: application/json" \
-H "authorization: Bearer $RUNHOOK_ADMIN_TOKEN" \
--data-binary '{"name":"echo","description":"Echo body","code":"export function run(ctx){ return { ok:true, body: ctx.body }; }"}'List functions:
curl -H "authorization: Bearer $RUNHOOK_ADMIN_TOKEN" http://localhost:8787/functionsGet one function (includes source):
curl -H "authorization: Bearer $RUNHOOK_ADMIN_TOKEN" http://localhost:8787/functions/echoUpdate a function (partial update: code and/or description):
curl -X PUT http://localhost:8787/functions/echo \
-H "content-type: application/json" \
-H "authorization: Bearer $RUNHOOK_ADMIN_TOKEN" \
--data-binary '{"description":"New description"}'Delete a function:
curl -X DELETE http://localhost:8787/functions/echo \
-H "authorization: Bearer $RUNHOOK_ADMIN_TOKEN"Webhook execution (public by default):
curl -X POST http://localhost:8787/webhook/echo \
-H "content-type: application/json" \
-d '{"hello":"world"}'The function must export run(ctx). Example:
export function run(ctx) {
return {
status: 200,
headers: { "content-type": "application/json" },
body: { ok: true, name: ctx.name, body: ctx.body },
};
}Context shape:
type ExecContext = {
name: string;
url: string;
path: string;
method: string;
headers: Record<string, string>;
rawHeaders: Array<[string, string]>;
body: string;
json: unknown | null;
query: Record<string, string>;
now: string;
};Return values:
Responseobject{ status, headers, body }string(plain text)- any JSON‑serializable object
SSE endpoint: GET /mcp/sse
Messages endpoint: POST /mcp/messages?session=...
Tools:
server.infoserver.aboutfunctions.listfunctions.createfunctions.getfunctions.updatefunctions.delete
Management endpoints are protected by the admin token.
Environment variables:
RUNHOOK_HOST(default:0.0.0.0)RUNHOOK_PORT(default:8787)RUNHOOK_TIMEOUT_MS(default:1500)RUNHOOK_NET_ALLOWLIST(comma‑separated allowlist; default: all)RUNHOOK_ADMIN_TOKEN(orRUNNHOOK_ADMIN_TOKEN)RUNHOOK_DB_PATH(default:runhook.db)
Build a single binary:
deno task buildRun the compiled binary:
./runhookdeno task testBuild locally:
docker build -t runhook:dev .Run:
docker run -p 8787:8787 -e RUNHOOK_ADMIN_TOKEN=secret runhook:dev- Update
deno.jsonversion (e.g.0.1.1) - Tag and push:
git tag v0.1.1 && git push origin v0.1.1
The GitHub Action publishes ghcr.io/<owner>/<repo> with tags:
0.1.10.1latest
Licensed under either of:
- Apache License, Version 2.0 (
LICENSE-APACHE) - MIT license (
LICENSE-MIT)