AgentHook / hosted pilot

Webhooks,
delivered home.

A public endpoint for your local application. Receive events while your machine is offline, then deliver them over an outbound connection when you return.

Durable queue · encrypted storage · explicit ACKs

Get connected.

Use Node.js 22+ and a local checkout of the AgentHook project. Run these commands from its directory. Cloudflare credentials are only needed to deploy the service.

01 / Create your inbox

Generate a local identity and register it. Save the webhook URL printed by this command.

npm ci
node bin/agenthook.js init \
  --server https://hook.house

Already have an identity? Run register instead of init. Back up agenthook.identity.json; its private key is required to decrypt your queue.

02 / Bring events home

Start your application on port 3000, then start the forwarder in another terminal.

node bin/agenthook.js listen \
  --server https://hook.house \
  --forward http://localhost:3000

WebSockets notify the client when events arrive. Polling takes over if that connection is unavailable.

03 / Send a webhook

Replace FINGERPRINT with the 64-character inbox fingerprint printed at registration. Set your webhook provider to this URL, with your chosen path.

curl -X POST 'https://hook.house/FINGERPRINT/github' \
  -H 'Content-Type: application/json' \
  -d '{"hello":"home"}'

The sender gets an empty 200 OK after durable storage. Your application receives POST /github with the original body and application signature headers. The forwarder ACKs completed 2xx responses except 202; failures retry.

A small API. A durable inbox.

Base URL: https://hook.house. Below, :key is the SHA-256 fingerprint of the raw Ed25519 public key. Consumer routes require signed owner requests; use the CLI or requestInbox from src/client.js to sign requests and decrypt responses.

Method / routePurpose
GET /healthPublic service health: {"ok":true}.
POST /:key/your/pathPublic ingestion into a registered inbox. Stores ordinary deliveries and returns an empty 200. MCP verification requests get the challenge echoed as JSON instead. No owner signature required.
POST /v1/inboxes/:key/registerCreate or re-register an inbox with proof of key ownership.
GET /v1/inboxes/:key/eventsClaim the oldest available event. Include format=hpke-x25519-sha256-aes256gcm-v1 and optional leaseSeconds=30. Returns a lease and zero or one encrypted events.
POST /v1/inboxes/:key/ackConfirm processing: {"ids":["EVENT_ID"],"lease":"LEASE_TOKEN"}.
POST /v1/inboxes/:key/nackRelease for retry. Same IDs and lease, with optional retryAfterSeconds and error.
POST /v1/inboxes/:key/renewExtend processing time. Same IDs and lease, with optional leaseSeconds.
GET /v1/inboxes/:key/statusInspect queue and processing counts.
GET /v1/inboxes/:key/receipts/:idInspect an event's delivery state. Stored error text is encrypted.
GET /v1/inboxes/:key/notificationsAuthenticated WebSocket upgrade. Availability hints only; claim events over signed HTTP.

Process with explicit confirmation

node bin/agenthook.js receive --server https://hook.house
# Finish processing, then acknowledge with the returned ID and lease:
node bin/agenthook.js ack --server https://hook.house \
  --id EVENT_ID --lease LEASE_TOKEN
node bin/agenthook.js status --server https://hook.house

receive claims an event; it does not ACK. Pass --identity /path/to/file.identity.json to each command if you use a different identity file.

Signing requests directly

Send x-agenthook-key (base64 raw public key), x-agenthook-time (epoch milliseconds), x-agenthook-nonce (32 lowercase hex characters), and x-agenthook-signature (base64 Ed25519 signature). Sign the UTF-8 string below, with actual newline separators:

METHOD
PATH_AND_QUERY
TIME
NONCE
SHA256_BODY_HEX

Use an uppercase method and the exact path and query, excluding the origin. Hash the raw request body bytes, or the empty body for GET. Requests expire after 60 seconds; nonce reuse is rejected.

Know what gets delivered.

This is a hosted pilot. Account-wide abuse controls, key recovery, and provider-specific freshness adapters are not implemented. Start with light use.

MCP Events: automatic challenge response.

Use your registered inbox URL, such as https://hook.house/FINGERPRINT/mcp, as the webhook destination for events/subscribe. AgentHook answers the MCP verification challenge immediately, even while your local client is offline. No tunnel or local challenge handler is needed.

For agents: supply the inbox URL as delivery.url, select delivery.mode: "webhook", and supply the provider-required whsec_ signing secret as delivery.secret. AgentHook is the receiver; event discovery and subscription calls go to the provider's MCP server.

A verification POST must include nonempty X-MCP-Subscription-Id, webhook-id, webhook-timestamp, and webhook-signature headers and this JSON body:

{"type":"verification","challenge":"YOUR_NONCE"}

The response is HTTP 200 with {"challenge":"YOUR_NONCE"}. Handshakes are not queued; ordinary event deliveries are encrypted and queued for the local consumer.

AgentHook detects the handshake format without verifying its signature or storing the signing secret. Verify ordinary event signatures in your consumer; offline delays can affect timestamp freshness checks. This supports the MCP echo handshake, not every provider-specific verification protocol.