# Hooklistener > Hooklistener captures, inspects, replays and forwards webhooks. It also gives you local tunnels, email inboxes for tests, real-time (WebSocket, Socket.IO, MQTT, SSE) sink endpoints and uptime monitors. Agents can use it with no account (anonymous endpoints), through the MCP server, or through the CLI. Base URL: https://app.hooklistener.com Full docs: https://docs.hooklistener.com ## Get a webhook URL with no account Use this when you need a URL to receive webhooks (Stripe, GitHub, your own service) and the user has no Hooklistener account yet. An anonymous endpoint lasts up to 24 hours, keeps up to 500 requests and accepts up to 60 requests a minute. You can create 10 per hour from one IP. 1. Create the endpoint: curl -X POST https://app.hooklistener.com/api/v1/anon/endpoints \ -H 'content-type: application/json' -d '{"ttl_seconds": 86400}' The response contains `id`, `webhook_url`, `viewer_token`, `claim_token`, `claim_url` and `expires_at`. Keep `viewer_token` and `claim_token` secret; they are shown only once. 2. Point the sender at `webhook_url`. Any method and body is accepted. 3. Read what arrived. Use the viewer token as a bearer token: curl https://app.hooklistener.com/api/v1/anon/endpoints/$ID/events \ -H "authorization: Bearer $VIEWER_TOKEN" The list returns a body preview per event (`body_truncated: true` when cut). Fetch one event for the full body: curl https://app.hooklistener.com/api/v1/anon/endpoints/$ID/events/$EVENT_ID \ -H "authorization: Bearer $VIEWER_TOKEN" 4. Hand `claim_url` to the user when they want to keep the endpoint. After signing in they can claim it into their organization: it becomes a regular endpoint and the captured requests are copied into it. Until the original `expires_at`, the anonymous `webhook_url` keeps accepting requests and captures them into the new endpoint (with that endpoint's plan limits, rate limit and mock responses), so senders do not break; point them at the new endpoint's `webhook_url` (`/w/...`, returned by the claim) before it expires. After a claim the viewer token no longer reads events; read them from the new endpoint. An agent that already has the user's API key can claim it directly: curl -X POST https://app.hooklistener.com/api/v1/anon/endpoints/$ID/claim \ -H "authorization: Bearer $HOOKLISTENER_API_KEY" \ -H 'content-type: application/json' \ -d '{"claim_token": "'$CLAIM_TOKEN'", "name": "Stripe staging"}' ## MCP server (recommended for coding agents) Streamable HTTP MCP server with OAuth: https://app.hooklistener.com/api/mcp - Claude Code: `claude mcp add --transport http hooklistener https://app.hooklistener.com/api/mcp` - Codex: `codex mcp add hooklistener --url https://app.hooklistener.com/api/mcp --oauth-resource https://app.hooklistener.com/api/mcp` then `codex mcp login hooklistener --scopes full_access` Useful tools: `create_endpoint`, `wait_for_request`, `get_request`, `list_requests`, `diagnose_request`, `verify_request_signature`, `validate_request`, `replay_request`, `save_request_case`, `run_endpoint_cases`, `create_inbox`, `wait_for_email`, `create_realtime_endpoint`, `wait_for_realtime_message`, `create_monitor`. Read the `hooklistener://capabilities` resource for the full list. Sessions list the webhook tools plus the toolsets for products the organization already uses (email, monitors, realtime, saved cases, rules, tunnels); send the `x-hooklistener-toolsets: all` header, or a comma-separated list, to choose explicitly. ## CLI - Install: `brew tap hooklistener/tap && brew install hooklistener` or `npm install -g hooklistener` - Authenticate: `hooklistener login` - Tunnel a local port: `hooklistener tunnel --port 3000` - Agent skill: `npx skills add hooklistener/skills --skill hooklistener-cli` ## REST API Authenticate with an API key (prefix `hklst_`) as `authorization: Bearer ` or `x-api-key: `. Create keys at https://app.hooklistener.com/organization/settings/api-keys. - `GET /api/v1/endpoints`, `POST /api/v1/endpoints` (`{"debug_endpoint": {"name": "..."}}`): list and create endpoints. Each endpoint receives webhooks at `https://app.hooklistener.com/w/`. - `GET /api/v1/endpoints/:id/requests`: captured requests. - `GET /api/v1/endpoints/:endpoint_id/cases`, `POST /api/v1/endpoints/:endpoint_id/cases/run`: saved request cases and replay runs. - `GET /api/v1/inboxes`, `POST /api/v1/inboxes` (`{"name": "..."}`, optional `slug`): list and create email inboxes. Each inbox receives mail at its `email_address`; plus-addresses (`+anything@...`) land in the same inbox. - `GET /api/v1/inboxes/:id`: one inbox, with `email_count`. - `GET /api/v1/inboxes/:id/emails`: captured emails, newest first, with `page`/`page_size` (max 100) and a `pagination` object. Filters: `since` (ISO 8601 timestamp with offset, or an email id: only emails after it), `to` (exact recipient, e.g. a plus-address), `from` and `subject_contains` (case-insensitive substrings). - `GET /api/v1/inboxes/:id/emails/:email_id`: the full email: `from`, `recipients`, `subject`, `headers`, `text_body`, `html_body`, plus `links` (http(s) URLs from the bodies) and `codes` (standalone 4-8 digit numbers, subject first; `codes[0]` is usually the one-time code). - `GET /api/v1/inboxes/:id/emails/wait`: long-poll for the next matching email. Same filters, plus `timeout` in seconds (default 30, max 60). Returns 200 with the full email as soon as one arrives, or immediately when one already exists after `since` (the oldest such email). Returns 204 with no body on timeout; call it again. Without `since` it only waits for emails that arrive during the call, so record `since` before triggering the email. Test a signup email from CI (use a unique plus-address per run so parallel runs never see each other's mail): INBOX=$(curl -s -X POST https://app.hooklistener.com/api/v1/inboxes \ -H "authorization: Bearer $HOOKLISTENER_API_KEY" -H 'content-type: application/json' \ -d '{"name": "ci-signup"}') INBOX_ID=$(echo "$INBOX" | jq -r .data.id) ADDRESS=$(echo "$INBOX" | jq -r .data.email_address | sed "s/@/+run-$RUN_ID@/") SINCE=$(date -u +%Y-%m-%dT%H:%M:%SZ) # ... sign up with $ADDRESS ... curl -s -G https://app.hooklistener.com/api/v1/inboxes/$INBOX_ID/emails/wait \ -H "authorization: Bearer $HOOKLISTENER_API_KEY" \ --data-urlencode "to=$ADDRESS" --data-urlencode "since=$SINCE" -d timeout=60 \ | jq -r '.data.codes[0]' ## Plan limits When a plan limit blocks an action, API and MCP errors include an `upgrade` object with `reason`, `resource`, `limit`, `plan`, `trial_available` and `upgrade_url`. Only a person can change the plan: give the `upgrade_url` to the user instead of retrying. A full endpoint answers webhooks with HTTP 403 and `reason: "over_limit"`.