Documentation

AnyHook Docs

A zero-SDK webhook relay. Point your sender at AnyHook, get automatic retries, a full event log, unified signature verification, and one-click replay. No code changes required.

Quick Start

AnyHook requires no SDK, no code changes, and no package installation. The only step is replacing your webhook URL in your sender's dashboard.

  1. 1
    Create an account
    Go to anyhook.net/sign-up and create a free account. No credit card required.
  2. 2
    Create an App
    An App represents one webhook source. From your dashboard, click New App, give it a name (e.g. “Stripe Production”), and select the source type. AnyHook generates your inbound URL and a signing secret.
  3. 3
    Configure your destination
    Enter the URL of your server endpoint — where AnyHook forwards events after receiving them. You can add multiple destinations per App.
  4. 4
    Point your sender at AnyHook
    In your sender's dashboard (e.g. Stripe → Developers → Webhooks), replace the existing webhook URL with your AnyHook inbound URL:
    https://in.anyhook.net/{user-slug}/{app-slug}
    Your first event will appear in the AnyHook event log within seconds.

Inbound URL Format

Every App gets a unique inbound URL:

https://in.anyhook.net/{user-slug}/{app-slug}
  • user-slug — your account identifier, visible in dashboard settings.
  • app-slug — unique per App, auto-generated from the app name but editable.

The inbound endpoint accepts any POST request. AnyHook returns HTTP 200within 50ms regardless of your destination endpoint's speed — the sender never waits.

Content types. AnyHook accepts application/json, application/x-www-form-urlencoded, and raw binary payloads. The original Content-Type header is preserved when forwarding.

Supported Sources

AnyHook auto-detects the sender from incoming headers and validates the payload signature at the edge. To enable verification, select your webhook provider when creating an App and paste the provider's webhook signing secret (e.g. Stripe's whsec_...).

Highly recommended. Without a signing secret, AnyHook cannot verify that incoming requests genuinely came from your provider. Unverified requests are still forwarded — adding the secret lets AnyHook reject forged requests at the edge before they enter your pipeline.
SourceSignature HeaderAlgorithm
StripeStripe-SignatureHMAC-SHA256 + timestamp
GitHubX-Hub-Signature-256HMAC-SHA256
ShopifyX-Shopify-Hmac-Sha256HMAC-SHA256 (Base64)
DiscordX-Signature-Ed25519Ed25519
SlackX-Slack-SignatureHMAC-SHA256 + timestamp
LinearLinear-SignatureHMAC-SHA256
SentrySentry-Hook-SignatureHMAC-SHA256
Vercelx-vercel-signatureHMAC-SHA256
PaddlePaddle-SignatureHMAC-SHA256
SendGridX-Twilio-Email-Event-Webhook-SignatureECDSA
IntercomX-Hub-SignatureHMAC-SHA256
SvixSvix-SignatureHMAC-SHA256
Lemon SqueezyX-SignatureHMAC-SHA256
Generic / CustomAny header you configureHMAC-SHA256

Signature validation failures are logged with full headers so you can diagnose mismatches without guessing. Invalid requests are rejected at the edge — they never reach your endpoint.

How Delivery Works

AnyHook uses an async, queue-based architecture to decouple your webhook sender from your backend:

  1. Edge receive — Cloudflare Workers accepts the POST, validates the signature, and returns 200 in under 50ms.
  2. Queue — The event is published to a durable message queue (Upstash QStash). The sender's HTTP connection is already closed.
  3. Forward — QStash delivers the event to AnyHook's forwarder, which POSTs the original payload to your destination endpoint.
  4. Log — Every step is recorded: inbound headers/body, delivery status, your endpoint's response code and body, latency.
Why this matters. Your sender (Stripe, GitHub, etc.) gets a fast 200 immediately. Your backend can take 5 seconds or 5 minutes to respond — the sender never retries on its own, because it already thinks delivery succeeded.
Payload size limit. Maximum payload per event: 512 KB (Free), 2 MB (Pro), 5 MB (Scale), custom (Enterprise). Oversized requests are rejected at the edge with 413 Payload Too Large — never silently dropped, never billed. The rejection is logged with full headers for debugging.
Delivery model. AnyHook is a real-time relay — events are forwarded as fast as they arrive with no artificial delay or concurrency cap. If your endpoint returns 429, AnyHook will back off and retry automatically. Per-destination concurrency limits are on our roadmap.

Retry Schedule

When your destination returns a retryable error or times out, AnyHook automatically retries using exponential backoff:

AttemptDelayPlan
1ImmediateAll
25 minutesAll
330 minutesAll
42 hoursPro, Scale, Enterprise
512 hoursPro, Scale, Enterprise
6–10Continued backoffScale, Enterprise
Delivery timeout. AnyHook waits up to 60s (Free), 120s (Pro), or 5 minutes (Scale+) for your endpoint to respond. If your handler needs even more time — for example, a multi-step AI agent — return HTTP 202 Accepted immediately and process asynchronously. AnyHook marks delivery as successful on 202.

Which responses trigger retries?

Not all errors are retryable. AnyHook classifies your endpoint's response to avoid wasting retry quota on errors that won't be fixed by trying again:

Status CodeActionWhy
2xx✅ Success — delivery completeYour endpoint accepted the event.
408 Request Timeout🔄 Retry with backoffTemporary timeout — may succeed on next attempt.
429 Too Many Requests🔄 Retry with backoffYour endpoint is rate limiting — give it time.
4xx (other: 400, 401, 403, 404, 422…)❌ Terminal — marked failed immediatelyClient error — the request is malformed or unauthorized. Retrying the same payload won't help.
5xx🔄 Retry with backoffServer error — likely temporary.
Network error / timeout🔄 Retry with backoffYour endpoint was unreachable.
Why 4xx is terminal. A 400 Bad Request or 422 Unprocessable Entitymeans your handler rejected the payload — retrying 5 times won't change the outcome. By marking it failed immediately, you get notified faster, your retry quota is preserved, and you can replaythe event once you've fixed your handler.

Each attempt is logged with the full response body and status code. Both 4xx terminal failures and retryable failures count toward the circuit breaker — 20 consecutive failures of any kind will trip it.

Failure notifications

AnyHook sends email alerts at specific thresholds rather than per event, so your inbox stays manageable:

  • 1st failure — immediate alert with status code and destination.
  • 5th consecutive failure — escalation alert with suggestions.
  • 20th consecutive failure — circuit breaker trips (see below), CB alert sent.

Maximum 3 emails per incident — no matter how many events fail. Any single successful delivery resets the counter.

Circuit Breaker

If AnyHook detects 20 consecutive delivery failures to the same destination (including terminal 4xx), the circuit breaker trips and begins a progressive escalation:

TripPause durationWhat happens
110 minutesDestination paused, email sent
230 minutesDestination paused, email sent
32 hoursDestination paused, email sent
424 hoursDestination paused, email sent
5PermanentDestination deactivated, email sent

How it works

  • Inbound keeps receiving. Your inbound URL stays active at all times — events from Stripe, GitHub, etc. are still accepted and stored. Only outbound delivery to your destination is paused.
  • After each pause expires, AnyHook sends a half-open probe — the next event tests your destination. If it succeeds, the circuit closes and everything resets.
  • Any single success resets everything — consecutive failure count and trip count both go back to zero.
  • Events received during a pause are stored with status circuit_open and can be replayed once your destination recovers.
  • You receive an email notification at each escalation step, including the destination URL, failure count, and pause duration.
  • If the destination is deactivated (Trip 5), you must manually re-enable it from the dashboard.

This progressive approach gives your server time to recover while protecting your event quota from cascading failures.

Event Log

Every inbound event is recorded regardless of delivery outcome. Each log entry includes:

  • Full inbound headers and body (as received from the sender)
  • Delivery status: queuedsuccess / retrying / failed / circuit_open / quota_exceeded
  • Your destination's HTTP response code and body (per attempt)
  • Latency in milliseconds
  • Attempt count and next retry time
  • Whether the event is a replay (is_replay: true)

Events are retained for your plan's retention window: 3 days (Free), 30 days (Pro), 90 days (Scale), up to 365 days (Enterprise). After that, the payload is permanently deleted.

Dashboard Replay

Any event in the log can be replayed from the dashboard with one click. A replay re-sends the original payload byte-for-byte to your current destination endpoint.

  • Navigate to Dashboard → Events
  • Find the event and click Replay
  • A new event is created with is_replay: true, linked to the original
Quota. Replays do not count against your monthly event quota.

API Replay (Single Event)

POST/api/v1/events/{'{id}'}/replayReplay a single event by ID

Publishes the original event payload to QStash for re-delivery. The new event is linked to the original via replay_of.

curl -X POST https://anyhook.net/api/v1/events/{event_id}/replay \
  -H "Authorization: Bearer {api_key}"

Returns 200 with the new event ID on success. Returns 410 Gone if the event is past its retention window.

Undelivered Events

GET/api/v1/apps/{'{slug}'}/events/undeliveredList events that failed to deliver

Returns events with status failed, circuit_open, or quota_exceeded.

ParamTypeDefaultDescription
sinceISO 860124h agoOnly events created after this timestamp
limitinteger100Max events to return (1–200)
curl "https://anyhook.net/api/v1/apps/stripe-prod/events/undelivered?since=2026-04-11T00:00:00Z&limit=50" \
  -H "Authorization: Bearer {api_key}"
{
  "count": 3,
  "events": [
    { "id": "evt_abc", "status": "failed", "created_at": "..." },
    ...
  ]
}

Batch Replay

POST/api/v1/apps/{'{slug}'}/replay-failedReplay all failed events for an app

Fetches all failed events matching the criteria and replays them in one call. Expired events (past retention) are skipped.

Body ParamTypeDefaultDescription
sinceISO 860124h agoOnly replay events created after this timestamp
limitinteger50Max events to replay (1–100)
curl -X POST https://anyhook.net/api/v1/apps/stripe-prod/replay-failed \
  -H "Authorization: Bearer {api_key}" \
  -H "Content-Type: application/json" \
  -d '{"since": "2026-04-11T00:00:00Z", "limit": 100}'
{
  "replayed": 3,
  "events": [
    { "original_id": "evt_abc", "new_id": "evt_xyz" },
    ...
  ]
}
Daily replay limit. Free: 100/day, Pro: 500/day, Scale: 1,000/day, Enterprise: unlimited.

Local Development

AnyHook works with local development out of the box — no special mode or CLI required. Two approaches:

1. Live forwarding via tunnel

Set your App's destination to a tunnel URL that points to your local machine. Any tunnel provider works:

# ngrok
ngrok http 3000
# → https://abc123.ngrok.io

# cloudflared
cloudflared tunnel --url http://localhost:3000

# localhost.run
ssh -R 80:localhost:3000 nokey@localhost.run

Copy the tunnel URL into your App's destination in the dashboard. AnyHook forwards webhooks there exactly like any other endpoint — retries, logging, and signature headers all work the same.

Tip. Keep your production inbound URL (https://in.anyhook.net/you/app) pointed at AnyHook while developing. You get real events captured in your log, and you can switch destinations between tunnel and production without touching Stripe.

2. Capture & replay to localhost

Let real production events accumulate in AnyHook, then replay any of them to your local environment whenever you need. No need to trigger test events manually — test against real payloads, unlimited times.

  • Switch your destination to your tunnel URL
  • Open Dashboard → Events and click Replay on any event
  • Or use the API: POST /api/v1/events/{id}/replay
  • The original payload is sent byte-for-byte to your current destination

This is especially useful for debugging edge cases — find the problematic event in production logs, replay it locally, and iterate until your handler works correctly.

Replays are free. Replays do not count against your monthly event quota. Replay the same event as many times as you need.

Encryption at Rest

All webhook payload data is encrypted at rest using AES-256-GCM before it reaches the database. This includes inbound headers, request bodies, and per-destination signing secrets.

Encryption uses a dedicated key (AES_SECRET_KEY) that is stored separately from the database credentials. A database breach without the encryption key yields only ciphertext — no plaintext webhook data, no signing secrets, no customer information.

What's encrypted

  • Webhook request headers & body (every event)
  • Provider signing secrets (Stripe webhook secret, etc.)
  • Per-destination AnyHook signing secrets
Zero plaintext secrets in the database. Every secret and every webhook payload is AES-256-GCM encrypted before storage. Even with full database access, an attacker sees only ciphertext.

Edge Verification

AnyHook validates webhook signatures at the Cloudflare edge in under 5ms. The validation happens before the event enters the queue — invalid or unsigned requests are dropped immediately.

Signature detection is automatic based on headers. When you create an App and select a source type, AnyHook stores the signing secret (encrypted at rest) and uses the correct algorithm for that provider.

Invalid requests. Requests that fail signature validation are rejected with HTTP 401 and logged with full headers for debugging. They never reach your destination endpoint.

Unsupported or custom sources

If your webhook source isn't in the supported list, select Generic when creating your App. AnyHook will:

  • Accept and queue every inbound POST — no inbound signature check.
  • Forward the original headers and body byte-for-byte to your destination.
  • Attach an AnyHook-Signature header so your endpoint can still verify the request came through AnyHook.

If your custom source uses HMAC-SHA256 signing, you can configure a signing secret under the Generic source type — AnyHook will verify it at the edge using the header name you specify. For sources with non-standard algorithms, skip inbound verification and rely on AnyHook-Signature for trust.

AnyHook-Signature

Every forwarded request includes an AnyHook-Signatureheader — a timestamped HMAC-SHA256 signature generated with your destination's signing secret.

AnyHook-Signature: t=1712934567,v1=5257a869...

To verify on your server:

// Node.js / TypeScript
import crypto from "crypto";

function verifyAnyHook(header: string, body: string, secret: string): boolean {
  const [tPart, vPart] = header.split(",");
  const timestamp = tPart.replace("t=", "");
  const signature = vPart.replace("v1=", "");

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${body}`)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature, "hex"),
    Buffer.from(expected, "hex")
  );
}

The signing secret is generated per-destination and available in your dashboard under App → Destinations → Signing secret. You can rotate it at any time via the API.

Custom Headers

You can attach custom headers to outbound requests on a per-destination basis. Common use cases:

  • Authorization: Bearer ... — authenticate with your API
  • x-api-key: ... — API key authentication
  • Any arbitrary header your endpoint requires

Custom header values are encrypted at rest and never exposed in the event log. Configure them in App → Destinations → Custom headers.

Original headers preserved. The original provider headers (Stripe-Signature, etc.) are forwarded byte-for-byte alongside your custom headers and AnyHook-Signature. Your existing SDK verification still works without changes.

Authentication

All API requests require a Bearer token in the Authorization header:

Authorization: Bearer ak_live_xxxxxxxxxxxxxxxx

Generate API keys from Dashboard → Settings → API Keys. Keys are scoped to your account and can be revoked at any time.

Rate limits. API endpoints are rate-limited to 60 requests per minute per user. If you exceed this limit, you'll receive 429 Too Many Requests with a Retry-After header.

Apps

GET/api/v1/appsList all apps
POST/api/v1/appsCreate a new app
GET/api/v1/apps/{'{slug}'}Get app details
PATCH/api/v1/apps/{'{slug}'}Update an app
DELETE/api/v1/apps/{'{slug}'}Delete an app

Create an app:

curl -X POST https://anyhook.net/api/v1/apps \
  -H "Authorization: Bearer {api_key}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Stripe Production",
    "source": "stripe",
    "destination_url": "https://api.example.com/webhooks"
  }'
{
  "id": "app_xxxxx",
  "name": "Stripe Production",
  "slug": "stripe-production",
  "inbound_url": "https://in.anyhook.net/you/stripe-production",
  "destinations": [
    { "url": "https://api.example.com/webhooks", "enabled": true }
  ]
}

Events

GET/api/v1/eventsList events (filterable by app, status, date range)
GET/api/v1/events/{'{id}'}Get event details including full payload

Query parameters for listing:

ParamTypeDescription
app_idstringFilter by app ID
statusstringFilter by status (queued, success, retrying, failed, circuit_open, quota_exceeded)
sinceISO 8601Events created after this timestamp
untilISO 8601Events created before this timestamp
limitintegerMax results (default 50, max 200)
offsetintegerPagination offset

Replay (API)

POST/api/v1/events/{'{id}'}/replayReplay a single event

See API Replay above for full details and examples.

Undelivered (API)

GET/api/v1/apps/{'{slug}'}/events/undeliveredList failed/undelivered events

See Undelivered Events above for full details and examples.

Batch Replay (API)

POST/api/v1/apps/{'{slug}'}/replay-failedReplay all failed events for an app

See Batch Replay above for full details and examples.

Plan Limits

FreePro ($20/mo)Scale ($90/mo)Enterprise
Events/month3,00050,000250,000Flexible
Daily cap100NoneNoneNone
Apps110100Flexible
Retries3510Flexible
Retention3 days30 days90 daysUp to 365 days
Delivery timeout60s120s5 minFlexible
Max payload512 KB2 MB5 MBCustom
OverageQueues next cycle$0.90/1k$0.90/1kContract
Daily replay limit1005001,000Unlimited

See full pricing details →

Usage & Quotas

AnyHook tracks usage on a natural month basis (e.g. “2026-04”). The counter resets at the start of each calendar month.

  • First attempt only — retries don't count against your quota.
  • Replays don't count — events with is_replay: true are excluded from billing.
  • Edge fast-check — quota is checked approximately at the edge for fast rejection. The database is the source of truth and syncs back to the edge periodically.
Free tier overage. Events over the monthly cap (or 100/day limit) are queued for the next billing cycle. Nothing is silently dropped — you'll see them with status quota_exceeded in your event log.
Paid tier overage. Pro and Scale plans bill overage at $0.90 per 1,000 events, rounded up to the nearest cent. We never silently drop paid traffic.

Overage Billing

When your monthly quota runs out, overage billing lets events keep flowing instead of being blocked. Available on Pro and Scale plans only.

How it works

  1. Set a monthly spending cap in Settings ($5 / $10 / $20 / $50, or Off).
  2. Once your plan's event quota is reached, AnyHook checks whether overage is enabled and your cap isn't exhausted.
  3. If allowed, the event is delivered normally and tagged as overage.
  4. At the start of the next month, overage is billed as a single line item on your Stripe invoice at $0.90 per 1,000 events (per-event, rounded to the nearest cent), capped at your spending limit.

Per-app toggle

Each app has an overage setting: Inherit default, Enabled, or Disabled. By default, apps inherit your account-level setting. Override per-app in App Settings → Overage Billing.

Account default

In Settings → Overage Billing, toggle “Enable overage by default for new apps” to automatically opt new apps in. Existing apps are not changed — update them individually.

Spending cap is a hard limit. Once your overage cost reaches the cap, further events are blocked with overage_cap_reached (HTTP 429) until the next billing cycle. The cap protects you from unexpected bills.

API Keys

API keys authenticate your requests to the AnyHook REST API. Manage them from Dashboard → Settings → API Keys.

  • Keys are prefixed with ak_live_ for production.
  • You can create multiple keys and label them (e.g. “CI/CD”, “Agent”).
  • Revoke a key instantly — revoked keys return 401 on next use.
  • Keys are shown only once on creation. Store them securely.
GET/api/v1/api-keysList all API keys (secrets masked)
POST/api/v1/api-keysCreate a new API key
POST/api/v1/api-keys/{'{id}'}/revokeRevoke an API key

Need help?

Email us at gba3124@gmail.com — we respond to every message. Check status.anyhook.net for live service health.