Skip to main content

Webhook inspector

A webhook endpoint is Cairn's requestbin. It gives you a URL that captures every request sent to it (method, path, query, headers, and body) and shows them as they arrive. Use one to see exactly what a service sends before you connect it to something real, or to find out why a receiver rejected a delivery.

Create an endpoint

The web app, the CLI, and MCP can't create endpoints yet, so use the REST API with a token (see Mint a personal access token):

curl -sS https://cairn.stump.wtf/v1/hooks \
-H "Authorization: Bearer $CAIRN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"title": "github push payloads", "request_cap": 100}'

Both fields are optional. request_cap is how many recent requests to keep. It defaults to 500, and the oldest requests drop off as new ones arrive. The response carries the addresses you need:

{
"id": "<id>",
"url": "https://cairn.stump.wtf/<id>",
"ingress_url": "https://cairn.stump.wtf/h/<id>",
"mcp": "mcp://cairn/hook/<id>",
"request_cap": 100,
"expires_at": "…",
"requests": []
}
AddressUse it to
ingress_urlReceive requests. Give it to the sender; it accepts any HTTP method without authentication.
urlWatch requests arrive, in a browser
mcpLet an agent read and subscribe to the same stream

Like any artifact, the endpoint expires after 7 days by default.

Send it something

curl -sS -X POST "https://cairn.stump.wtf/h/<id>?source=test" \
-H 'Content-Type: application/json' \
-H 'X-Hub-Signature-256: sha256=0123abcd' \
-d '{"hello": "cairn"}'

Every captured request gets the same answer: 200 with the body captured. The sender never gets anything else back, and can't read what was captured. The other responses:

StatusMeaning
404No such endpoint, or it has expired
413The body is over the capture limit (5 MiB)
429Too many requests from one address, or to one endpoint

Watch requests

  • In a browser: open the endpoint's url. Requests stream in with highlighted JSON and a status mix. You can react to a single request, say 👀 on the one that broke. Requests don't take comments.

  • From an agent: read the resource mcp://cairn/hook/<id> to get the endpoint and its retained requests, newest first, and subscribe to hear about new ones. Agents can only read the stream. Nothing writes into it except the ingress URL.

  • From a script:

    curl -sS "https://cairn.stump.wtf/v1/hooks/<id>/requests" # newest first
    curl -sS "https://cairn.stump.wtf/v1/hooks/<id>/requests/<seq>" # one request
    curl -sS "https://cairn.stump.wtf/v1/hooks/<id>/requests/<seq>/body" -o body # its exact bytes
    curl -sSN "https://cairn.stump.wtf/v1/hooks/<id>/stream" # live, as server-sent events
The viewer link shows everything captured

Anyone with the endpoint's url can read every captured body. Don't point a sender at an inspector if its payloads carry secrets or personal data you wouldn't paste into a shared document.

What gets recorded

Cairn stores each request as inert data. It never runs, follows, or fetches anything in a payload. Before it stores the headers, it lower-cases their names and drops credentials and connection-level headers: Authorization, Cookie, Set-Cookie, Proxy-Authorization, and hop-by-hop headers such as Connection. Signature headers such as X-Hub-Signature-256 and X-Cairn-Signature are kept, which is what makes the inspector useful for signature problems.

Debug a delivery to Switchboard

When Switchboard rejects a signed webhook, it doesn't store the payload, so there's nothing to look at afterwards. Send a copy to an inspector to see what was actually on the wire:

  1. Create an endpoint, as above.
  2. Add its ingress_url as a second target next to the Switchboard URL. For a forge such as GitHub or Gitea, add a second webhook with the same secret. For Cairn's own outbound webhooks, the instance operator adds it to Cairn's target list; Cairn sends the same signed body to every target.
  3. Trigger the event again.
  4. Compare the capture with what Switchboard expects:
    • The signature. Recompute the HMAC over the exact captured bytes (download them from /body; don't copy text out of the formatted view) and compare it with the captured signature header. See Webhook signature mismatches.
    • The event headers. For Cairn, X-Cairn-Event should be artifact.created, and X-Cairn-Event-Id should equal the body's event_id.
    • The content type and size. Switchboard reads JSON bodies and enforces a size limit.
  5. Remove the extra target when you're done.