open source · AGPL-3.0 · single Go binary

A webhook inbox your agents can read.

hookinlet receives webhooks into named inboxes, verifies their signatures, and keeps a queryable history. Agents read it over REST, SSE, or MCP, with no glue code.

localhost:8080
# give any MCP client the inbox (Claude Code shown); # the token is printed when you create an inbox $ claude mcp add --transport http hookinlet \ http://localhost:8080/mcp \ --header "Authorization: Bearer $HOOKINLET_AGENT_TOKEN" agent → wait_for_messages(inbox: "stripe", after: 41) tool ← 1 message #42 verified 19:04:11Z untrusted: { event_type: "invoice.paid", … } agent → "Invoice paid. Marking order 1182 as active."
how it works

Senders post. hookinlet holds. Agents read when they're ready.

Every inbox has its own random ingest URL, its own signing secret and its own sequence. Senders get their 2xx only after the event is committed; readers catch up whenever they run.

1 · sender
Any webhook source
stripe → /in/q3V9xR1h…
github → /in/Zk2mP8uW…
slack → /in/b6A0yTnc…
2 · inboxstripe
Verify → append → retain
#40customer.created✓
#41invoice.created✓
#42invoice.paid✓
Signature checked on arrival. Each event gets the next sequence number. Kept for the retention you set.
3 · agent
Reads at its own pace
REST · poll
SSE · stream
MCP · tools
1 binary
static Go, or one Docker image
SQLite
built in, nothing else to run
3 protocols
REST · SSE · MCP
AGPL-3.0
read every line
consume

Three ways to read an inbox. Same cursor everywhere.

A cursor is the last sequence number you handled. Pass it back and you resume exactly where you stopped — across restarts, reconnects and protocols.

REST

poll

Page through history. Good for cron jobs and batch agents.

$ curl -s -H "Authorization: Bearer $TOKEN" \
    'localhost:8080/v1/inboxes/github/messages
    ?after=117&limit=2' \
    | jq -c '.messages[] | {seq, event_type}'
{"seq":118,"event_type":"push"}
{"seq":119,"event_type":"pull_request"}

# keep .next as your cursor

SSE

stream · optional

Live messages as they land. Reconnect with Last-Event-ID and you get every later message exactly once.

$ curl -N -H "Authorization: Bearer $TOKEN" \
    -H 'Last-Event-ID: 118' \
    localhost:8080/v1/inboxes/github/stream

retry: 3000

id: 119
event: message
data: {"seq":119,"inbox":"github",
data:  "event_type":"pull_request", …}

: ping

MCP

tools for agents

One endpoint, four read-only tools. wait_for_messages is the whole polling loop in one call, and what the sender wrote is kept under untrusted.

# endpoint, with an agent token
http://localhost:8080/mcp

# tools
list_inboxes()
query_messages(inbox, after, limit, bodies)
get_message(inbox, seq)
wait_for_messages(inbox, after, timeout_seconds)

# in a session
→ wait_for_messages("slack", after=56)
← #57 verified
    untrusted: { event_type: "app_mention", … }
console

See what is arriving.

Open /admin on your server and sign in with the admin token. It does what the command line does, in a browser.

  • ✓Every message: body, headers and the exact bytes
  • ✓Follow new messages live
  • ✓New inboxes, with a form for each kind of sender
  • ✓Secrets, tokens, retention and backups

Built into the binary; turn it off with HOOKINLET_ADMIN_UI=false. What a sender wrote is always shown as text, never as HTML.

The hookinlet console showing a Stripe inbox: a list of verified messages on the left and the selected invoice event with its JSON body on the right, under a note that everything the sender wrote is untrusted.
sample data

Small surface. The boring parts done right.

Signature presets

Stripe, GitHub, Slack, Standard Webhooks (Svix), a configurable HMAC and a shared token, all checked on the exact bytes sent. Set verifier: stripe and the secret.

Replay-safe cursors

Per-inbox sequence numbers, assigned in commit order. Keep the last one you handled and you see every message exactly once. You are told if retention removed messages you had not read, or if the inbox was restored or recreated.

Retention per inbox

Keep by age, by count, or both. Messages expire on your schedule, never because a reader was slow.

Single binary

One static Go binary or one Docker image. SQLite by default, so there's nothing else to run.

Config as code

Declare inboxes in a YAML file and run hookinlet apply. It creates what's missing, changes only what differs, and never deletes. Secrets come from the environment, never the file.

Agent tokens

Issue as many named read-only tokens as you need, each scoped to some inboxes or all. Replace one by issuing a new one and revoking the old, with no gap. Every message says whether it was verified.

Exact bytes kept

Bodies are stored byte for byte. Read them back from /raw and the sender's signature still verifies.

Bring your own host

A VPS, a container, a box under your desk: one process per data directory, with TLS in front. No telemetry, no phone-home.

Committed before 2xx

A sender gets its 2xx only after the message is committed and synced to disk. Forged signatures get a 401 and are never stored. Verifier secrets are encrypted at rest and tied to their inbox.

# inboxes.yaml  ·  hookinlet apply -f inboxes.yaml
inboxes:
  - slug: stripe
    verifier: stripe
    secret_env: STRIPE_WEBHOOK_SECRET
    retention_days: 30
  - slug: github
    verifier: github
    secret_env: GITHUB_WEBHOOK_SECRET
    retention_max_count: 50000
  - slug: slack
    verifier: slack
    secret_env: SLACK_SIGNING_SECRET
    retention_days: 7

Run it yourself today. Let us run it later.

hookinlet

AGPL-3.0 · free

Every feature on this page. Your host, your data, your retention.

  • ✓REST, SSE and MCP
  • ✓Signature presets and generic HMAC
  • ✓SQLite by default, one binary
  • ✓Community support on GitHub
$ go install github.com/hookinlet/hookinlet/cmd/hookinlet@latest
$ hookinlet serve && hookinlet apply -f inboxes.yaml

hookinlet Cloud

coming soon

The same inboxes on a hosted endpoint, with nothing to deploy. Tell us what you'd use it for and we'll shape it around that.

Join the waitlist Five short questions about your use case, volume and what you would pay. It opens a form on another site. We email once, when Cloud opens. No newsletter, no sharing. Privacy