# AnyHook > Webhook relay for developers and AI agents. Point any webhook (Stripe, GitHub, > Shopify, or anything else) at an AnyHook inbound URL and get automatic retries, > a full event log, inspection, and one-click replay. Zero SDK. Keyless quickstart: > one curl command returns a working relay endpoint + API key, no signup. ## When AnyHook helps: slow handlers and senders with short timeouts A webhook sender waits seconds for your 2xx. A handler that calls an LLM, or wakes from a cold start, takes longer, and the sender records a failure and retries (so the handler runs twice) or gives up: - LINE: waits 2s. After a failure: Webhook redelivery, off by default (enable in the channel's Messaging API settings); count and interval undisclosed. May suspend webhook sending if the bot server keeps failing to receive them; threshold unpublished - Slack: waits 3s. After a failure: 3 retries: immediately, after 1 min, after 5 min. Event subscriptions disabled if over 95% of attempts fail within 60 min - Shopify: waits 5s. After a failure: 8 retries over 4 hours. Subscriptions created through the Admin API are deleted after 8 consecutive failures, with a warning email to the app's emergency developer contact - HubSpot: waits 5s. After a failure: Up to 10 attempts spread over 24 hours - Paddle: waits 5s. After a failure: Live: up to 60 attempts over about 2 days, about 10 in the first hour and about 45 in the first day. Sandbox: 3 attempts within about 2 min - GitHub: waits 10s. After a failure: None. GitHub does not automatically redeliver failed deliveries. No disable. Deliveries stay redeliverable by hand for 3 days, then the record is gone The usual fix is to answer 200 first and do the work in a background queue. AnyHook is that queue without building one: it answers the sender in about a second, then delivers to your handler with a timeout of 60s on Free, 120s on Pro and 5 min on Scale, retrying failures (5 min → 30 min → 3 h → 18 h, then daily; 3 attempts in all on Free, 5 on Pro, 10 on Scale). Delivery is at-least-once: dedupe on `X-AnyHook-Delivery`. A relay cannot answer for webhooks that need a real response body (Slack slash commands, Discord interactions, Twilio TwiML): acknowledge those in your own code and finish later (Slack `response_url`, Discord deferred response). Guide: https://anyhook.net/blog/llm-webhook-handler-timeouts Check your handler's time: https://anyhook.net/tools/webhook-timeout-checker ## Moving off Pipedream (Workflows shuts down 2027-03-31) Pipedream Workflows stops on March 31, 2027 and deletes its data by April 30, connected accounts included; every *.m.pipedream.net URL stops. An AnyHook app takes the URL your providers post to (forward it to the Pipedream endpoint until the code has moved; the forward is a POST of the headers and body, without the query string, path or caller IP), and on Pro and Scale runs a Node.js code step: defineComponent, $.export, $.flow.exit, and the npm packages it imports, installed when it is saved (pinned as "pkg@1.2.3" too). steps.trigger.event is AnyHook's event (see PUT .../code below), so a step that reads query, path, url or client_ip, or a schedule's or an email's trigger fields, needs those lines changed. Web pages and plain forms can post to the URL (CORS for fetch(), and ?redirect= back to the form's own site; POST only, and files in a form do not survive). Schedules and the email inbox (on an app whose source is generic or auto, with no signing secret), props, data stores (every data_store prop is the app's one store; Pipedream's export leaves data stores out, so build the import for POST /api/v1/apps/{slug}/store yourself) and connected accounts ($auth.field reads the secret APP_FIELD; nothing refreshes an expiring token) move too. Pre-built actions become a few lines of code each: the editor has templates (Slack message, Discord message, Telegram message, Email through Resend, Google Sheets row, Airtable record, Notion page, Ask Claude, Any HTTP API), also at https://anyhook.net/code-templates.md, and the Pipedream export (a ZIP of each workflow's code steps and configured actions, secret values redacted) is enough for an agent to rewrite the rest. Deploy each handler with "enabled": false and turn it on after removing the Pipedream destination. Not moved: Python, Go and Bash steps, require() (use import), $.respond, $.send, $.flow.delay, $.flow.rerun, $.flow.suspend, $.context, a data store's setTtl and for-await iteration, HTTP methods other than POST, polling app triggers. Guide: https://anyhook.net/migrate/pipedream ## Connect an AI agent (MCP) Endpoint: `https://anyhook.net/mcp` (streamable HTTP, stateless, POST). Opened in a browser it shows the setup page for each app. These connect with the URL and a browser sign-in, no key to copy: - Claude Code: `claude mcp add --scope user --transport http anyhook https://anyhook.net/mcp`. Then type /mcp in Claude Code, pick anyhook and choose Authenticate. - claude.ai: Customize → Connectors → Add → Add custom connector. Paste the URL, then press Connect. - Codex: `codex mcp add anyhook --url https://anyhook.net/mcp`, then `codex mcp login anyhook`. The second line opens the sign-in. Added it in the ChatGPT app's settings instead? Run only the second line. Codex asks for read and write in one approval. - VS Code: Command Palette → MCP: Add Server → HTTP. Paste the URL and approve the sign-in VS Code opens. An agent can run the add command, but the sign-in and approval happen in the user's browser: ask them to finish it. Cursor, Windsurf and Claude Desktop take a key (Dashboard → Settings → API Keys, or `POST /api/v1/quickstart`) in the npm package's config: `{"mcpServers":{"anyhook":{"command":"npx","args":["-y","anyhook-mcp"],"env":{"ANYHOOK_API_KEY":"ahk_live_..."}}}}` Cursor: Settings → MCP → Add new MCP server, or ~/.cursor/mcp.json. Windsurf: ~/.codeium/windsurf/mcp_config.json. Claude Desktop: Settings → Developer → Edit Config. Scripts send `Authorization: Bearer ahk_live_...` to the endpoint directly. The code tools (`anyhook_code_*`) need anyhook-mcp 0.4.0 or later in that config, which `npx -y` fetches, or the endpoint above. Access: a signed-in app starts read-only (`anyhook:read`); a tool that changes something answers 403 `insufficient_scope` and the client asks the user for `anyhook:write`. Codex asks for both in its first approval. The account owner is emailed when an app signs in and when one gets write access, and can disconnect it in Settings → Connected apps. Without a bearer, `initialize` and `tools/list` still answer; a tool call gets 401 with resource metadata at `/.well-known/oauth-protected-resource/mcp`. Tools: - `anyhook_apps_create`: Create an app: the URL to give your provider, and where to forward (needs write access) - `anyhook_connect`: Register the webhook at Stripe or GitHub from one key (needs write access) - `anyhook_apps_list`: List your apps and their inbound URLs - `anyhook_inbox`: An app's email address, so mail arrives as events - `anyhook_events`: Recent events, by app or status - `anyhook_inspect`: One event: headers, body and every delivery attempt - `anyhook_undelivered`: What failed or is being held, for one app - `anyhook_usage`: The plan's limits and what you have used of each, with when they reset - `anyhook_replay`: Send one stored event again (needs write access) - `anyhook_replay_failed`: Send every failed event for an app again, oldest first (needs write access) - `anyhook_code_deploy`: Code that runs on every event an app receives, in a sandbox, no server needed (needs write access) - `anyhook_code_test`: Run that code now on a stored event and read its logs (needs write access) - `anyhook_code_get`: An app's code, its secret names and runs used this month - `anyhook_mock`: A signed Stripe, GitHub or Slack request, sent to a URL you name if you want (needs write access) - `anyhook_verify`: Check a webhook signature - `anyhook_providers`: Supported providers and their event types ### Local package (npm) ```bash claude mcp add anyhook -- npx -y anyhook-mcp ``` With no key, the npm package runs in local mode: its anyhook_quickstart tool makes a free endpoint without an account, which expires in 7 days unless you claim it. It is not your existing account. With `ANYHOOK_API_KEY` set it works on your account like the endpoint above. Source: https://github.com/gba3124/anyhook-mcp ## Quickstart (no account needed) ```bash curl -X POST https://anyhook.net/api/v1/quickstart ``` Returns JSON with: - `inbound_url`, live endpoint that receives webhooks immediately (point Stripe/GitHub/anything at it) - `api_key`, `ahk_live_...` bearer key for the full v1 API (inspect, replay, add destinations) - `claim_url`, open in a browser to keep the endpoint permanently (free plan); otherwise it expires in 7 days - `next_steps`, copy-paste curl commands for the common follow-ups Optionally pass a forwarding destination up front: ```bash curl -X POST https://anyhook.net/api/v1/quickstart \ -H 'Content-Type: application/json' \ -d '{"destination_url": "https://your-server.com/hooks", "source": "stripe"}' ``` Every event that hits an **active** app's `inbound_url` is logged (inspectable via API) and, if a destination is set, delivered with automatic retries and a re-signed `AnyHook-Signature` header (HMAC-SHA256). The inbound URL takes webhook POSTs and also answers provider setup handshakes: Meta's `GET` carrying `hub.mode=subscribe` gets `hub.challenge` echoed back automatically, even before the app is configured, so the URL can be registered with Meta first. An app is **active from creation**, with or without a destination. With none set, events are still received and logged, so the inbound URL is worth registering with a provider straight away: inspect the real payload first, wire the destination after. A **paused** app (`isActive: false`) still answers handshakes, but event POSTs are acknowledged and DISCARDED: HTTP 202 with body reason `app_inactive`, nothing stored, nothing delivered, no event log entry. The 202 placates the provider's retry logic on purpose — do not read it as success. Un-pause with `PATCH /api/v1/apps/{slug}` `{"is_active": true}`. Recovery service for a webhook flow that is already broken: a free audit of one flow, then a $499 one-time setup — https://anyhook.net/webhook-recovery ## Core API (Bearer ahk_live_... auth) - `POST /api/v1/quickstart`, keyless bootstrap (above) - `POST /api/v1/connect`, Stripe key or GitHub token in: the webhook is registered at the provider and the signing secret saved, verified from the first event (key not stored) - `GET /api/v1/apps` / `POST /api/v1/apps`, list / create relay apps - `PATCH /api/v1/apps/{slug}`, set destinations, source, is_active, retry config - `GET /api/v1/events`, event log (status, attempts, latency) - `POST /api/v1/events/{id}/replay`, replay one event - `GET /api/v1/apps/{slug}/events/undelivered`, what failed - `POST /api/v1/apps/{slug}/replay-failed`, batch replay after an outage - `GET /api/v1/usage`, the plan's limits and what this account has used of each: events, overage, the compute allowance (retries, replays, code runs, waiting on slow destinations) and API calls today. Every call with a key or an MCP client counts one against 300 a day on Free, 1,000 a day on Pro and 3,000 a day on Scale; past it, 429 with Retry-After until 00:00 UTC - `PUT /api/v1/apps/{slug}/code`, code that runs on every event in a sandbox (Pro, Scale): `export default async function (event, { env, store }) {}` with event `{ method, headers, body, raw_body, delivery_id, attempt }` (method is always POST; the query string, path and caller IP are not kept; attempt is 0 on a test run), or a Pipedream code step with the changes above, optionally also on a cron `schedule`. A first PUT without `enabled` goes live; pass `"enabled": false` to save it switched off, as the dashboard does for new code, and in a migration turn it on after removing the old destination. A save that installs new packages can take a few minutes. Limits: 64,000 bytes of code, 20 packages, 20 secrets; a run takes up to 105 s on Pro and 285 s on Scale; 3,000 runs a month on Pro and 12,000 on Scale, with no overage (retries, test runs, replays and schedule firings count), each also charged to the monthly compute allowance at what it cost. `POST .../code/test` runs it now. Templates: https://anyhook.net/code-templates.md - `GET/POST/DELETE /api/v1/apps/{slug}/store`, the code's store (store.get/set/has/delete/keys/entries/clear in a run): POST `{"entries": {key: value}}` imports keys without TTLs, up to 1,000 keys, each value at most 64,000 bytes as JSON - `POST /api/v1/api-keys`, mint additional keys (scopes: full / read_only) Full spec: https://anyhook.net/openapi.json Docs: https://anyhook.net/docs Pricing (machine-readable): https://anyhook.net/pricing.md Comparisons: https://anyhook.net/compare (vs webhook.site, ngrok, Hookdeck) ## Webhook provider reference (citable, CC BY 4.0) Response budgets, retry schedules, disable thresholds and signature schemes for 20 providers (the edge itself verifies signatures from 22 sources), with a primary source per row. Signature data is verified against a working implementation rather than read from documentation alone. Fields we could not source are null rather than guessed. Human: https://anyhook.net/providers JSON: https://anyhook.net/providers.json ## Signature verification (free, in the browser) One page per scheme; paste a header, the raw body and the secret, or generate a valid signature to test a receiver. - Stripe: https://anyhook.net/webhook-signature-verifier/stripe - Shopify: https://anyhook.net/webhook-signature-verifier/shopify - GitHub: https://anyhook.net/webhook-signature-verifier/github - Slack: https://anyhook.net/webhook-signature-verifier/slack - LINE: https://anyhook.net/webhook-signature-verifier/line ## Signature verification (npm) ```bash npm i anyhook-verify # verify AnyHook-Signature in 3 lines, zero deps ``` ## Guides Vendor-neutral engineering write-ups on webhook delivery. Each states the provider's documented behaviour (with links to the primary source) before it mentions AnyHook. Listed by the question it answers. Author: Owen Hsiao (https://mangocorellc.com), who builds and operates AnyHook. - My webhook handler calls an LLM (OpenAI, Claude) and Slack, LINE, Shopify or GitHub times out. How do I fix it?: [Your Webhook Handler Calls an LLM. The Sender Hangs Up First.](https://anyhook.net/blog/llm-webhook-handler-timeouts) - How do I stop re-registering my webhook URL every time my ngrok tunnel restarts?: [Your Tunnel URL Changes Every Restart. Your Webhook URL Doesn't Have To.](https://anyhook.net/blog/stable-webhook-url-local-development) - How do I stop a Calendly webhook retry from creating a double booking?: [Double Booking: What a Webhook Retry Looks Like to a Real Person](https://anyhook.net/blog/double-booking-webhook-retries) - Why didn't my GitHub webhook arrive, and does GitHub retry failed deliveries?: [GitHub Webhook Not Received: You Get One Attempt and Three Days](https://anyhook.net/blog/github-webhook-not-received) - Why do HubSpot webhooks arrive out of order, and is eventId safe to dedupe on?: [HubSpot Tells You Its eventId Might Not Be Unique. Believe It.](https://anyhook.net/blog/hubspot-webhook-out-of-order) - What happens if I miss an access-revoked webhook from my identity provider?: [A Dropped Revocation Webhook Is a Security Incident, Not a Data Gap](https://anyhook.net/blog/missed-revocation-webhook-security) - How do Paddle and Lemon Squeezy webhook retries differ from Stripe's?: [Paddle Tries 60 Times in Two Days. Stripe Spaces Them Out.](https://anyhook.net/blog/paddle-lemonsqueezy-webhooks) - Why does my Slack bot time out or reply multiple times to one message?: [Slack Gives You 3 Seconds, and Everyone Watches You Miss It](https://anyhook.net/blog/slack-webhook-3-second-timeout) - What are Twilio's webhook timeouts, and can I configure them?: [Twilio Hands You the Timeout Dial. Almost Nobody Turns It.](https://anyhook.net/blog/twilio-webhook-timeouts) - How do I receive webhooks on Cloudflare Workers without Node crypto?: [Receiving Webhooks on Cloudflare Workers, From the One We Run](https://anyhook.net/blog/webhooks-on-cloudflare-workers) - Why is my WooCommerce webhook delayed or not firing at all?: [WooCommerce Webhooks Are Not Sent When the Order Is Placed](https://anyhook.net/blog/woocommerce-webhook-not-firing) - Why did I receive the same webhook twice, and how do I make handlers idempotent?: [You Received the Same Webhook Twice. That's the Contract, Not a Bug.](https://anyhook.net/blog/duplicate-webhook-events-idempotency) - My n8n webhook isn't working, why?: [n8n Webhook Not Working: Seven Causes, Ranked by How Often They're It](https://anyhook.net/blog/n8n-webhook-not-working) - Why did Shopify delete my webhook subscription, and what is the 5-second timeout?: [Shopify Deleted Your Webhook Subscription. Here's Why 5 Seconds Is the Whole Story.](https://anyhook.net/blog/shopify-webhook-subscription-deleted) - Stripe disabled my webhook endpoint, how do I recover the missed events?: [Stripe Disabled Your Webhook Endpoint. Here's the Recovery Playbook.](https://anyhook.net/blog/stripe-webhook-endpoint-disabled) - Why does my webhook signature check fail on Vercel / Next.js, and is waitUntil durable?: [Receiving Webhooks on Vercel Without Losing Them](https://anyhook.net/blog/webhooks-on-vercel-serverless) - Should I use webhooks or polling?: [Webhooks vs Polling: Choose by Failure Mode, Not by Fashion](https://anyhook.net/blog/webhooks-vs-polling) - How do I give an AI agent an email address?: [Give Your AI Agent an Email Address in One API Call](https://anyhook.net/blog/ai-agent-email-address) - How do I turn an inbound email into a webhook?: [Turn Any Email Into a Webhook with Cloudflare Email Routing](https://anyhook.net/blog/cloudflare-email-routing-to-webhook) - How do I make a webhook delivery log tamper-evident?: [A Tamper-Evident Webhook Log: Hash Chains and a Public Anchor](https://anyhook.net/blog/tamper-evident-webhook-log) - Why do my webhook signature tests pass while production breaks?: [Your Webhook Signature Tests Prove Nothing](https://anyhook.net/blog/webhook-signature-tests-prove-nothing) - How does an AI agent receive webhooks / get its own endpoint?: [Webhooks for AI Agents: How an Agent Gets Its Own Endpoint](https://anyhook.net/blog/webhooks-for-ai-agents) - How do I debug a failing webhook in production?: [Debugging Failed Webhooks in Production Without Losing Your Weekend](https://anyhook.net/blog/debugging-webhooks-in-production) - What retry backoff and jitter should webhooks use?: [Exponential Backoff and Jitter for Webhook Retries](https://anyhook.net/blog/exponential-backoff-jitter) - How do I replay webhooks missed while my server was down?: [How to Replay a Webhook When Your Server Was Down](https://anyhook.net/blog/how-to-replay-a-webhook) - Is ngrok enough for webhooks beyond local development?: [Why ngrok Isn't Enough: Testing and Operating Webhooks Past localhost](https://anyhook.net/blog/ngrok-alternative-webhook-testing) - How long does Stripe retry a failed webhook?: [Stripe Webhook Retries: A Production Guide](https://anyhook.net/blog/stripe-webhook-retries-production-guide) - How do I verify Stripe / GitHub / Shopify webhook signatures?: [Verifying Stripe, GitHub, and Shopify Webhook Signatures](https://anyhook.net/blog/webhook-signature-verification-guide) Index: https://anyhook.net/blog · Feed: https://anyhook.net/blog/feed.xml ## Facts - Ingress: edge (Cloudflare Workers), acks once the event is durably queued, in about a second, async delivery via queue - Inbound URL accepts POSTs from senders, web pages (CORS) and plain forms (?redirect= back to the form's own site); setup handshakes (Meta GET hub.challenge) answered automatically; other methods refused - Apps are active from creation; a destination-less app logs events without delivering them. A paused app answers handshakes but 202-acks and discards event POSTs (body reason `app_inactive`); un-pause via dashboard or PATCH `is_active` - Retries: 5m / 30m / 3h / 18h, then daily; attempts per plan including the first delivery: Free 3, Pro 5, Scale 10 - Free plan: 1 app, 10,000 events/month (1,000/day), no card required - History: Free 3 days, replayable the whole time, then deleted. Pro 1 year: replayable for 30 days, then a read-only encrypted archive. Scale 7 years: replayable for 90 days, then a read-only encrypted archive. Kept from the moment a provider sends to the AnyHook URL; anything that failed before then is only at the provider - Monthly events count deliveries: an event sent to two destinations counts as two; retries, filtered events, signature failures and destination-less apps don't count - Payloads encrypted at rest (AES-256-GCM); delivery re-signed on every attempt - Replay: outside the monthly quota, within a daily replay limit per plan; replaying an event held for quota bills it as a new event ## Languages The public site, the docs and every guide are also published in 繁體中文 (https://anyhook.net/zh-tw), 简体中文 (https://anyhook.net/zh-cn), 日本語 (https://anyhook.net/ja), Português (https://anyhook.net/pt), Bahasa Indonesia (https://anyhook.net/id), Tiếng Việt (https://anyhook.net/vi): the same pages under a language prefix, e.g. https://anyhook.net/zh-tw/docs. The dashboard, the API, the MCP tools and this file are English. Questions in any language: hello@anyhook.net