Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/hunvreus/heypi/llms.txt

Use this file to discover all available pages before exploring further.

This is the simplest possible Heypi agent—no Slack, no Discord, no Telegram. The webhook adapter exposes a plain JSON HTTP API that any script, CI pipeline, or backend service can call with curl. The agent accepts a message, optionally saves a note to a local Markdown file, and returns a thread ID and run ID you can poll for the result. It is the fastest way to see Heypi working end-to-end without configuring any chat platform.

What’s Included

HTTP API

A JSON webhook endpoint at /webhook/notes/messages. No chat platform required—any HTTP client works.

Thread Management

Each POST creates or continues a thread. Follow-up messages to the same threadId maintain conversation context.

Async Run Polling

The initial POST returns immediately with a runId. Poll /threads/:id/runs/:runId until the run completes.

Note Storage

The save_note tool appends timestamped Markdown entries to state/notes.md with an optional topic label.

Setup

1

Clone or copy the example

cp examples/webhook-notes/.env.example examples/webhook-notes/.env
2

Install dependencies

pnpm install
3

Set environment variables

Edit examples/webhook-notes/.env. The only required custom value is the webhook secret:
OPENAI_API_KEY=
HEYPI_WEBHOOK_SECRET=dev-secret-change-me
HEYPI_WEBHOOK_PORT=3000
Set HEYPI_WEBHOOK_SECRET to any random string. It is used as a Bearer token in all requests. HEYPI_WEBHOOK_PORT defaults to 3000.
4

Run the agent

pnpm run dev:webhook
# or directly:
node index.js
The server listens on http://127.0.0.1:3000 by default.

Configuration

const app = createHeypi({
  state: { root: stateRoot },
  http: {
    host: "127.0.0.1",
    port: Number(process.env.HEYPI_WEBHOOK_PORT ?? 3000),
  },
  adapters: [
    webhook({
      name: "notes",
      secret: required("HEYPI_WEBHOOK_SECRET"),
    }),
  ],
  agent: agentFrom("./agent", {
    model: "openai/gpt-5-mini",
    tools: [...coreTools({ bash: false, write: false, edit: false }), saveNote],
  }),
  runtime: { root: workspace("./workspace") },
});
The adapter name determines the URL prefix: webhook({ name: "notes" }) mounts at /webhook/notes. Core tools are restricted to read-only (bash: false, write: false, edit: false) since this is a minimal notes agent.

The save_note Tool

const saveNote = tool<{ note: string; topic?: string }>({
  name: "save_note",
  description: "Append a short note to local Markdown.",
  parameters: Type.Object({
    note: Type.String({ description: "Concise note to save." }),
    topic: Type.Optional(Type.String({ description: "Optional topic label." })),
  }),
  execute: async ({ note, topic }) => {
    await mkdir(dirname(notesPath), { recursive: true });
    const prefix = topic ? `[${topic}] ` : "";
    await appendFile(notesPath, `- ${new Date().toISOString()} ${prefix}${note}\n`, "utf8");
    return "note saved";
  },
});
The tool appends a single line to state/notes.md. An optional topic field wraps the note with a label, e.g. [billing] Remember to add a smoke test.

curl Examples

Replace dev-secret-change-me with your HEYPI_WEBHOOK_SECRET value, and substitute <threadId> / <runId> with values returned by previous calls.

Start a New Thread

curl -X POST http://127.0.0.1:3000/webhook/notes/messages \
  -H "authorization: Bearer dev-secret-change-me" \
  -H "content-type: application/json" \
  -d '{"user":"demo","text":"Remember that the launch checklist needs a billing smoke test"}'
The response contains a threadId, runId, and the initial status:
{
  "ok": true,
  "threadId": "thread_abc123",
  "runId": "run_xyz789",
  "status": "running"
}

Follow Up on an Existing Thread

Pass the threadId from the previous response to continue the conversation:
curl -X POST http://127.0.0.1:3000/webhook/notes/messages \
  -H "authorization: Bearer dev-secret-change-me" \
  -H "content-type: application/json" \
  -d '{"user":"demo","threadId":"thread_abc123","text":"Also add a rollback plan to the checklist"}'

Check Run Status

Poll this endpoint until status is done, failed, or cancelled:
curl http://127.0.0.1:3000/webhook/notes/threads/thread_abc123/runs/run_xyz789 \
  -H "authorization: Bearer dev-secret-change-me"
A completed run returns something like:
{
  "ok": true,
  "threadId": "thread_abc123",
  "runId": "run_xyz789",
  "status": "done",
  "text": "note saved"
}

Pending Approval

If a tool uses confirm, the status endpoint returns status: "pending_approval" along with an approval object describing the pending action. In that case, the approval must be handled through one of the configured approver identities (set via HEYPI_APPROVERS). This example’s save_note tool has no confirmation step, so runs proceed directly to done.
The webhook adapter does not expose a dedicated REST approve endpoint. Approvals in webhook integrations are handled through the configured approver system. See the Discord Project example for a working approval gate.

Agent Folder

agent/
  SOUL.md     # "You are a minimal note-taking assistant."
  AGENTS.md   # Save useful notes with save_note; keep replies short
SOUL.md is intentionally minimal—a single sentence. This keeps the agent focused on its one job without behavioral noise. AGENTS.md instructs the agent to call save_note for notes and ask for the note to save if the message is not clearly a note.

Synchronous Mode

For short, fast tasks you can request a synchronous response by adding "sync": true to the request body. The HTTP call blocks until the run completes and returns the final output directly instead of a runId to poll:
curl -X POST http://127.0.0.1:3000/webhook/notes/messages \
  -H "authorization: Bearer dev-secret-change-me" \
  -H "content-type: application/json" \
  -d '{"user":"demo","text":"Save a note: deploy window is Saturday 02:00 UTC","sync":true}'
Synchronous mode ties up the HTTP connection for the duration of the run. Use it only for fast, tool-light requests. For anything involving multiple tool calls or long reasoning chains, use the default async pattern with polling.

Reply URL Callback

The webhook adapter supports an optional replyUrl field in the request body. When set, Heypi POSTs the final run output to that URL instead of (or in addition to) the poll response. This lets upstream systems receive results passively without polling:
curl -X POST http://127.0.0.1:3000/webhook/notes/messages \
  -H "authorization: Bearer dev-secret-change-me" \
  -H "content-type: application/json" \
  -d '{
    "user": "demo",
    "text": "Note: staging deploy passed all checks",
    "replyUrl": "https://your-service.example.com/heypi-callback"
  }'
This example does not configure replyHosts, so replyUrl callbacks are disabled by default. To allow callbacks to a specific host, add a replyHosts allowlist to the webhook adapter options.

Build docs developers (and LLMs) love