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.
- 1Create an accountGo to anyhook.net/sign-up and create a free account. No credit card required.
- 2Create an AppAn 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.
- 3Configure your destinationEnter the URL of your server endpoint — where AnyHook forwards events after receiving them. You can add multiple destinations per App.
- 4Point your sender at AnyHookIn 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.
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_...).
| Source | Signature Header | Algorithm |
|---|---|---|
| Stripe | Stripe-Signature | HMAC-SHA256 + timestamp |
| GitHub | X-Hub-Signature-256 | HMAC-SHA256 |
| Shopify | X-Shopify-Hmac-Sha256 | HMAC-SHA256 (Base64) |
| Discord | X-Signature-Ed25519 | Ed25519 |
| Slack | X-Slack-Signature | HMAC-SHA256 + timestamp |
| Linear | Linear-Signature | HMAC-SHA256 |
| Sentry | Sentry-Hook-Signature | HMAC-SHA256 |
| Vercel | x-vercel-signature | HMAC-SHA256 |
| Paddle | Paddle-Signature | HMAC-SHA256 |
| SendGrid | X-Twilio-Email-Event-Webhook-Signature | ECDSA |
| Intercom | X-Hub-Signature | HMAC-SHA256 |
| Svix | Svix-Signature | HMAC-SHA256 |
| Lemon Squeezy | X-Signature | HMAC-SHA256 |
| Generic / Custom | Any header you configure | HMAC-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:
- Edge receive — Cloudflare Workers accepts the POST, validates the signature, and returns 200 in under 50ms.
- Queue — The event is published to a durable message queue (Upstash QStash). The sender's HTTP connection is already closed.
- Forward — QStash delivers the event to AnyHook's forwarder, which POSTs the original payload to your destination endpoint.
- Log — Every step is recorded: inbound headers/body, delivery status, your endpoint's response code and body, latency.
413 Payload Too Large — never silently dropped, never billed. The rejection is logged with full headers for debugging.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:
| Attempt | Delay | Plan |
|---|---|---|
| 1 | Immediate | All |
| 2 | 5 minutes | All |
| 3 | 30 minutes | All |
| 4 | 2 hours | Pro, Scale, Enterprise |
| 5 | 12 hours | Pro, Scale, Enterprise |
| 6–10 | Continued backoff | Scale, Enterprise |
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 Code | Action | Why |
|---|---|---|
| 2xx | ✅ Success — delivery complete | Your endpoint accepted the event. |
| 408 Request Timeout | 🔄 Retry with backoff | Temporary timeout — may succeed on next attempt. |
| 429 Too Many Requests | 🔄 Retry with backoff | Your endpoint is rate limiting — give it time. |
| 4xx (other: 400, 401, 403, 404, 422…) | ❌ Terminal — marked failed immediately | Client error — the request is malformed or unauthorized. Retrying the same payload won't help. |
| 5xx | 🔄 Retry with backoff | Server error — likely temporary. |
| Network error / timeout | 🔄 Retry with backoff | Your endpoint was unreachable. |
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:
| Trip | Pause duration | What happens |
|---|---|---|
| 1 | 10 minutes | Destination paused, email sent |
| 2 | 30 minutes | Destination paused, email sent |
| 3 | 2 hours | Destination paused, email sent |
| 4 | 24 hours | Destination paused, email sent |
| 5 | Permanent | Destination 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_openand 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:
queued→success/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
API Replay (Single Event)
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
Returns events with status failed, circuit_open, or quota_exceeded.
| Param | Type | Default | Description |
|---|---|---|---|
| since | ISO 8601 | 24h ago | Only events created after this timestamp |
| limit | integer | 100 | Max 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
Fetches all failed events matching the criteria and replays them in one call. Expired events (past retention) are skipped.
| Body Param | Type | Default | Description |
|---|---|---|---|
| since | ISO 8601 | 24h ago | Only replay events created after this timestamp |
| limit | integer | 50 | Max 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" },
...
]
}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.
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.
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
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.
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-Signatureheader 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 APIx-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.
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.
429 Too Many Requests with a Retry-After header.Apps
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
Query parameters for listing:
| Param | Type | Description |
|---|---|---|
| app_id | string | Filter by app ID |
| status | string | Filter by status (queued, success, retrying, failed, circuit_open, quota_exceeded) |
| since | ISO 8601 | Events created after this timestamp |
| until | ISO 8601 | Events created before this timestamp |
| limit | integer | Max results (default 50, max 200) |
| offset | integer | Pagination offset |
Replay (API)
See API Replay above for full details and examples.
Undelivered (API)
See Undelivered Events above for full details and examples.
Batch Replay (API)
See Batch Replay above for full details and examples.
Plan Limits
| Free | Pro ($20/mo) | Scale ($90/mo) | Enterprise | |
|---|---|---|---|---|
| Events/month | 3,000 | 50,000 | 250,000 | Flexible |
| Daily cap | 100 | None | None | None |
| Apps | 1 | 10 | 100 | Flexible |
| Retries | 3 | 5 | 10 | Flexible |
| Retention | 3 days | 30 days | 90 days | Up to 365 days |
| Delivery timeout | 60s | 120s | 5 min | Flexible |
| Max payload | 512 KB | 2 MB | 5 MB | Custom |
| Overage | Queues next cycle | $0.90/1k | $0.90/1k | Contract |
| Daily replay limit | 100 | 500 | 1,000 | Unlimited |
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: trueare 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.
quota_exceeded in your event log.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
- Set a monthly spending cap in Settings ($5 / $10 / $20 / $50, or Off).
- Once your plan's event quota is reached, AnyHook checks whether overage is enabled and your cap isn't exhausted.
- If allowed, the event is delivered normally and tagged as overage.
- 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.
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
401on next use. - Keys are shown only once on creation. Store them securely.
Need help?
Email us at gba3124@gmail.com — we respond to every message. Check status.anyhook.net for live service health.