Skip to main content
The goal: a GitHub pull_request event wakes an agent, which reviews the diff and posts a summary. The provider talks to a URL; the agent wakes on it.

1. Mint the agent’s URL

🔴 The secret is in this response and in no other. webhooks() reports secret_set: true and never the value — a secret a list can return is a secret in every log that rendered a list. If you lose it, delete the endpoint and mint a new one; there is no read-back.

2. Point the provider at it

Paste hook.url into GitHub’s webhook settings (or Stripe’s, or Linear’s) and give it the secret. Naïve verifies the signature and refuses anything that fails — your agent never sees unverified traffic. The delivery must carry two headers: The window is five minutes. An unknown or inactive endpoint answers 404 — the same answer as a wrong slug — so the URL is never an existence oracle.

3. Signing it yourself

If you are the one calling the URL — a backend of yours, a test, a provider with no built-in signing — use the SDK helper rather than rebuilding the string:
Two details that verify fine locally and fail only as a security property.The timestamp is part of the signed content, not a header beside it. A body-only signature is replayable forever, and a timestamp the MAC does not cover is editable by whoever captured the request.And rawBody must be the exact bytes you send. Re-serialising the object reorders keys and the signature stops matching — sign the string, then send that same string.

4. What the agent receives

The delivery becomes a task with source: "webhook" and the provider’s body in payload:
The call returns as soon as the row is durably written. The agent’s work never runs on the provider’s clock, so a slow review does not time out GitHub’s delivery and trigger a retry storm. Write the instructions so the agent knows to read payload:

5. List and revoke

Revoking answers 404 when it removed nothing — a delete that matched nothing is an error you can see, not a silent success.

6. Watch it react, live

Watching does not pin the agent awake — the stream is a view over the log, not a hold on the worker — so leaving a tail open does not change what the agent costs.

One URL per agent, or one per provider?

One endpoint is bound to exactly one agent, and the binding is by the endpoint’s own slug rather than anything in the provider’s payload. So:
  • Two providers, one agent — mint two endpoints on the same agent. Both wake it; payload tells them apart.
  • One provider, two agents — mint one endpoint per agent and register both URLs with the provider. Name them (--name github-pr, --name github-issues) so the list still reads six months later.
Agents never wake on each other’s traffic, because the subscription filters on the delivery’s own hook slug.