AnyHook ドキュメント
Webhook 中継サービスです。送信元の Webhook の URL を AnyHook に向けるだけで、自動リトライ、すべてを記録するイベントログ、統一された署名検証、ワンクリックでの再送が使えます。SDK は必要ありません。
クイックスタート
AnyHook には SDK もパッケージも必要ありません。送信元のダッシュボードで Webhook の URL を差し替えるだけで、ハンドラーはこれまでと同じリクエストを受け取り続けます。あらかじめ考えておくことが 1 つあります。リトライと再送には送信元の元のタイムスタンプが付いたままです。そのため、署名に有効期限がある送信元(Stripe、Slack など、/providers に許容範囲の記載があるもの)では、それらを受け入れるために AnyHook-Signature を検証してください。
最速の方法(アカウント不要):curl -X POST https://anyhook.net/api/v1/quickstart を実行すると、すぐに使える受信 URL と API キーがその場で手に入ります。AI エージェントや手早いテストに向いています。返された claim_url でアカウントに紐付けないと、エンドポイントは 7 日で期限切れになります。返された inbound_url を送信元に設定し、GET /api/v1/events と GET /api/v1/events/{id} でイベントを読み出します。各コマンドはレスポンスの next_steps に載っています。
- 1アカウントを作成するanyhook.net/sign-up で無料アカウントを作成します。クレジットカード登録は不要です。
- 2App を作成するApp は Webhook の送信元ごとに作る受信設定で、送信元 1 つを表します。新しいアカウントでは、最初にこのフォームが開きます。名前(例:「Stripe Production」)と、必要に応じてお使いのサーバーの URL を入力します。AnyHook は受信した各イベントをこの URL に転送します。空欄のままにすると、App は転送せずにすべてを記録します。転送先はいつでも追加でき、Free では App ごとに最大 3 つまでです。
- 3送信元を AnyHook に向けるApp のページには、受信 URL と、主な送信元でそれを入力する場所(例:Stripe → Developers → Webhooks)が表示されます。そこで、以前の Webhook の URL の代わりに次の受信 URL を貼り付けます。
https://in.anyhook.net/{user-slug}/{app-slug}ページは受信を待ち受けているので、最初のリクエストは数秒以内にページに表示されます。まだ送信できるものがない場合は、同じページで Send a test event(テストイベントを送信)を押します。
受信 URL の形式
App にはそれぞれ、次の形式の一意な受信 URL が割り当てられます。
https://in.anyhook.net/{user-slug}/{app-slug}user-slug:アカウントの識別子です。ダッシュボードの設定で確認できます。app-slug:App ごとに一意な識別子です。App の名前から自動生成されますが、編集もできます。
受信エンドポイントは、あらゆる POST リクエストを受け付けます。設定時の検証ハンドシェイクにも応答します。hub.mode=subscribe を含む GET(Meta の hub.challenge チェック)には、App の設定前でも自動でそのまま値を返します。それ以外のメソッドには 405 を返します。Web ページや通常の HTML フォームからも送信できます(コードハンドラーを参照)。AnyHook は、イベントを永続キューに入れた時点で、転送先エンドポイントの速度に関係なく約 1 秒で HTTP 200 を返します。送信元が転送先を待つことはありません。
application/json、application/x-www-form-urlencoded、XML、プレーンテキストなどのテキストのボディは、AnyHook がバイト単位でそのまま保存し、元の Content-Type のまま転送します。ボディはテキストとして保存されるため、バイナリのペイロードや、マルチパートフォームに含まれるファイルは、元のままの形では届きません。対応する送信元
AnyHook は受信したヘッダーから送信元を自動で判別し、ペイロードの署名をエッジで検証します。検証を有効にするには、App の作成時に Webhook のプロバイダーを選び、そのプロバイダーの Webhook 署名シークレット(例:Stripe の whsec_...)を貼り付けます。
| 送信元 | 署名ヘッダー | アルゴリズム |
|---|---|---|
| Stripe | Stripe-Signature | HMAC-SHA256 + タイムスタンプ |
| GitHub | X-Hub-Signature-256 | HMAC-SHA256 |
| Meta (Instagram / Facebook / Threads) | 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 + タイムスタンプ |
| LINE | x-line-signature | HMAC-SHA256 (Base64) |
| Xero | x-xero-signature | HMAC-SHA256 (Base64) |
| Linear | Linear-Signature | HMAC-SHA256 |
| Sentry | Sentry-Hook-Signature | HMAC-SHA256 |
| Vercel | x-vercel-signature | HMAC-SHA1 |
| Paddle | Paddle-Signature | HMAC-SHA256 + タイムスタンプ |
| SendGrid | X-Twilio-Email-Event-Webhook-Signature | ECDSA P-256 |
| Twilio | X-Twilio-Signature | HMAC-SHA1 |
| Intercom | X-Hub-Signature | HMAC-SHA1 |
| HubSpot | X-HubSpot-Signature-v3 | HMAC-SHA256 (v3) / SHA-256 (v1/v2) |
| Standard Webhooks (Svix, OpenAI, Replicate) | webhook-signature / Svix-Signature | HMAC-SHA256 + タイムスタンプ |
| Clerk | Svix-Signature | HMAC-SHA256(Svix 経由) |
| Resend | Svix-Signature | HMAC-SHA256(Svix 経由) |
| Lemon Squeezy | X-Signature | HMAC-SHA256 |
| WooCommerce | X-WC-Webhook-Signature | HMAC-SHA256 (Base64) |
| PayPal | PAYPAL-TRANSMISSION-SIG | RSA-SHA256 |
| Generic(汎用)/カスタム | なし | 検証なし |
検証に失敗したリクエストはエッジで拒否され、エンドポイントには届きません。拒否された件数は App のページに表示されます。毎日最初の 5 件は、ヘッダーとボディを含めて rejected としてイベントログに残るため、拒否の理由を確認できます。
Meta:Instagram、Facebook、Threads
3 つとも Graph API の同じ Webhook 仕様なので、App 1 つですべてに対応できます。Meta は GitHub と同じ X-Hub-Signature-256 ヘッダーで署名します。AnyHook は X-Github-Event で両者を区別します。このヘッダーは GitHub が必ず送り、Meta は決して送りません。
Meta は、コールバック URL が hub.mode=subscribe を含む GET に対して hub.challenge をレスポンスボディ全体としてそのまま返すまで、その URL を保存しません。AnyHook が代わりに応答するので、受信 URL は 1 回目で保存できます。
| AnyHook 側 | Meta App Dashboard 側 |
|---|---|
| Source:Meta | App settings → Basic |
| App Secret | ここからコピーして AnyHook に入力 |
| Verify Token(任意) | Webhooks → Verify token:同じ文字列を入力 |
| 受信 URL | Webhooks → Callback URL:ここに貼り付け |
Meta は失敗した配信を、間隔を広げながら 36 時間リトライし、その後は破棄します。AnyHook はエンドポイントへの配信を試す前にイベントを保存するため、デプロイや障害があっても、Meta のリトライ期間を消費することはありません。
配信の仕組み
AnyHook は非同期のキューベースのアーキテクチャで、Webhook の送信元とバックエンドを切り離します。
- エッジで受信:Cloudflare Workers が POST を受け付けて署名を検証し、イベントを永続化した時点で、約 1 秒で 200 を返します。
- キュー:イベントは永続メッセージキュー(Upstash QStash)に投入されます。この時点で、送信元の HTTP 接続はすでに閉じています。
- 転送:QStash がイベントを AnyHook の転送処理に届け、転送処理が元のペイロードを転送先エンドポイントに POST します。
- 記録:受信したヘッダーとボディ、配信ステータス、エンドポイントのレスポンスコードとボディ、レイテンシまで、すべての段階を記録します。
413 Payload Too Large として拒否されます。黙って破棄されることも、課金されることもありません。その件数は App のページに表示されます。429 を返すと、AnyHook は間隔を空けてリトライします。配信の保証
保証の内容をはっきり書いておきます。保証があいまいな中継サービスは、そのあいまいさをコードに押し付けることになるからです。
- 配信は少なくとも 1 回(at-least-once)で、1 回限り(exactly-once)ではありません。リトライこそがこの製品の中身であり、レスポンスが失われたあとのリトライは新しい配信と区別できません。同じイベントを 2 回受け取ることがときどきあります。そうならないと約束できる中継サービスはなく、約束しているところはうまくいった場合の話をしているだけです。ハンドラーは冪等にする必要があります。重複排除のキーには、AnyHook が
X-AnyHook-Deliveryで転送するイベント ID を使ってください。 - 順序は保証しません。リトライと並行配信があるため、あとのイベントが、まだリトライ中の前のイベントより先に届くことがあります。AnyHook より上流でも、ほとんどの送信元は順序を約束していません。順序は到着時刻ではなく、ペイロード内のフィールド(バージョンや
updated_at)で判断してください。 - 署名で重複排除をしないでください。
AnyHook-Signatureは、リトライが許容範囲から外れないよう、配信試行のたびにあえて新しいタイムスタンプで再署名されます。同じイベントでも、2 回の配信の署名は異なります。変わらないのはイベントのidです。 - 200 は保存済みを意味します。エッジが 200 を返すのは、イベントを永続化した後だけです。キューが使えなくても予備の保存先が 2 つあり、そのどちらにも保存できないときにだけ 503 を返します。受信 URL が 503 を返したときは何も保存されておらず、送信元自身のリトライが安全網になります。保存したふりをせずに 503 を返すのは、そのためです。
リトライスケジュール
転送先がリトライ対象のエラーを返したりタイムアウトしたりすると、AnyHook は指数バックオフで自動的にリトライします。
| 配信試行 | 間隔 | プラン |
|---|---|---|
| 1 | すぐに | すべて |
| 2 | 5 分後 | すべて |
| 3 | 30 分後 | すべて |
| 4 | 3 時間後 | Pro、Scale、Enterprise |
| 5 | 18 時間後 | Pro、Scale、Enterprise |
| 6-10 | それぞれ 24 時間後 | Scale、Enterprise |
HTTP 202 Accepted を返して非同期で処理してください。AnyHook は 202 を配信成功として扱います。リトライされるレスポンスは?
すべてのエラーがリトライの対象になるわけではありません。AnyHook はエンドポイントのレスポンスを分類し、再度試しても直らないエラーで、配信試行もコンピュート枠も消費しないようにしています。
| ステータスコード | 動作 | 理由 |
|---|---|---|
| 2xx | 成功:配信完了 | エンドポイントがイベントを受け付けました。 |
| 3xx | リトライしない:すぐに失敗として記録 | リダイレクトには従いません。転送先には最終的な URL を指定してください。 |
| 408 Request Timeout | バックオフ付きでリトライ | 一時的なタイムアウトです。次の配信試行で成功する可能性があります。 |
| 429 Too Many Requests | バックオフ付きでリトライ | エンドポイントがレート制限中です。時間を置きます。 |
| 4xx(上記以外:400、401、403、404、422…) | リトライしない:すぐに失敗として記録 | クライアントエラーです。リクエストの形式が不正か、認証されていません。同じペイロードでリトライしても解決しません。 |
| 5xx | バックオフ付きでリトライ | サーバーエラーです。一時的なものである可能性が高いです。 |
| ネットワークエラー/タイムアウト | バックオフ付きでリトライ | エンドポイントに到達できませんでした。 |
400 Bad Request や 422 Unprocessable Entity は、ハンドラーがペイロードを拒否したことを意味します。リトライしても結果は変わりません。すぐに失敗として記録すれば、通知が早く届き、リトライにコンピュート枠が使われることもありません。ハンドラーを修正したら、そのイベントを再送できます。配信試行はそれぞれ、レスポンスボディ全体とステータスコードとともに記録されます。リトライしない 4xx の失敗も、リトライ対象の失敗も、サーキットブレーカーのカウントに含まれます。種類を問わず 20 回連続で失敗すると、サーキットブレーカーが作動します。
失敗の通知
AnyHook はイベントごとではなく、決まったしきい値でメール通知を送ります。そのため、届くメールが増えすぎることはありません。
- 1 回目の失敗:ステータスコードと転送先を記載したアラートをすぐに送ります。
- 5 回連続の失敗:対処の提案を含むエスカレーションのアラートを送ります。
- 20 回連続の失敗:サーキットブレーカーが作動し(後述)、そのアラートを送ります。
メールは、上に挙げたしきい値ごとに 1 通、そのあとはサーキットブレーカーの段階ごと(一時停止のたびと無効化)に 1 通です。イベントごとに 1 通送ることは決してありません。配信が 1 回でも成功すると、カウンターはリセットされます。
サーキットブレーカー
同じ転送先への配信が 20 回連続で失敗すると(リトライしない 4xx を含む)、サーキットブレーカーが作動し、段階的に停止時間を延ばしていきます。
| 作動回数 | 停止時間 | 内容 |
|---|---|---|
| 1 | 10 分 | 転送先を一時停止、メールを送信 |
| 2 | 30 分 | 転送先を一時停止、メールを送信 |
| 3 | 2 時間 | 転送先を一時停止、メールを送信 |
| 4 | 24 時間 | 転送先を一時停止、メールを送信 |
| 5 | 無期限 | 転送先を無効化、メールを送信 |
仕組み
- 受信は続きます。受信 URL は常に有効で、Stripe や GitHub などからのイベントはそのまま受け付けて保存されます。一時停止するのは、転送先への配信だけです。
- 停止時間が過ぎるたびに、AnyHook はハーフオープンのプローブを送ります。次のイベントで転送先を試し、成功すればサーキットはクローズに戻り、すべてがリセットされます。
- 1 回でも成功すれば、すべてリセットされます:連続失敗回数も作動回数も 0 に戻ります。
- 一時停止中に受信したイベントは、ステータス
circuit_openで保存され、転送先が復旧したら再送できます。 - 段階が上がるたびに、転送先 URL、失敗回数、停止時間を記載したメール通知が届きます。
- 転送先が無効化された場合(5 回目の作動)、ダッシュボードから手動で再度有効にする必要があります。
この段階的な方式により、サーバーに復旧の時間を与えつつ、応答しないエンドポイントへのリトライでコンピュート枠が消費されるのを防ぎます。
イベントログ
受信 URL に届いたイベントは、配信の結果にかかわらずすべて記録されます。記録が始まるのは、プロバイダーの向き先を AnyHook にした時点からです。それより前に失敗したイベントは、プロバイダー側にしか残っていません(Stripe の場合は Webhook を再送する方法を参照)。各ログエントリには次の内容が含まれます。
- 受信したヘッダーとボディの全体(送信元から受け取ったまま)
- 配信ステータス:
queued→success/retrying/failed/rejected/circuit_open/quota_exceeded。rejectedは、AnyHook がイベントを拒否したこと(署名検証の失敗、または転送先 URL のブロック)を意味します。配信は一度も試行されておらず、再送できません。 - 転送先の HTTP レスポンスコードとボディ(配信試行ごと)
- レイテンシ(ミリ秒)
- 配信試行の回数と次のリトライ時刻
- 再送かどうか(
is_replay: true)
イベントの保存期間はプランによって異なります。Free:3 日。その間はずっと再送でき、その後は削除されます。Pro:1 年。最初の 30 日間は再送でき、その後は読み取り専用の暗号化アーカイブになります。Scale:7 年。最初の 90 日間は再送でき、その後は読み取り専用の暗号化アーカイブになります。Enterprise:柔軟に対応。
is_active: false)、受信 URL は 202 を返しますが、何も保存しません。そのためプロバイダーがエンドポイントを無効化することはありませんが、イベントは失われます。必要なトラフィックが届く前に、一時停止を解除してください。ダッシュボードからの再送
ログにあるイベントは、どれでもダッシュボードからワンクリックで再送できます。再送では、保存したペイロードをバイト単位でそのまま、そのイベントの転送先に送ります。その後で転送先の URL を変更していれば、新しい URL に送ります。
- Dashboard → Events を開く
- イベントを見つけて Replay(再送)をクリックする
- 元のイベントに紐付いた、
is_replay: trueの新しいイベントが作成される
API からの再送(単一イベント)
元のイベントのペイロードを、再配信のために QStash に投入します。新しいイベントは replay_of で元のイベントに紐付けられます。
curl -X POST https://anyhook.net/api/v1/events/{event_id}/replay \
-H "Authorization: Bearer {api_key}"成功すると、新しいイベント ID とともに 200 を返します。イベントが再送可能期間を過ぎている場合は 410 Gone を返します。
未配信のイベント
ステータスが failed、circuit_open、quota_exceeded のイベントを返します。
| パラメーター | 型 | デフォルト | 説明 |
|---|---|---|---|
| since | ISO 8601 | 24 時間前 | このタイムスタンプ以降に作成されたイベントのみ |
| limit | integer | 100 | 返すイベントの最大数(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": "..." },
...
]
}一括再送
条件に一致する失敗したイベントを、古いものから順に、1 回の呼び出しで上限件数まで再送します。すでに再送済みのイベントと、再送可能期間を過ぎたイベントはスキップされます。
| ボディのパラメーター | 型 | デフォルト | 説明 |
|---|---|---|---|
| since | ISO 8601 | 24 時間前 | このタイムスタンプ以降に作成されたイベントのみ再送 |
| limit | integer | 50 | 再送するイベントの最大数(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" },
...
]
}ローカル開発
AnyHook は、特別なモードや CLI なしで、そのままローカル開発に使えます。方法は 2 つあります。
1. トンネル経由のライブ転送
App の転送先を、ローカルマシンを指すトンネルの URL に設定します。トンネルのサービスはどれでも使えます。
# 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
ダッシュボードで、トンネルの URL を App の転送先にコピーします。AnyHook は、ほかのエンドポイントとまったく同じように Webhook を転送します。リトライ、ログ、署名ヘッダーもすべて同じように動作します。
https://in.anyhook.net/you/app)に向けたままにしておきます。実際のイベントがログに記録され、Stripe 側には触れずに、転送先をトンネルと本番環境の間で切り替えられます。2. 記録したイベントを localhost に再送
本番環境の実際のイベントを AnyHook にためておき、必要なときにどれでもローカル環境へ再送します。テストイベントを手動で発生させる必要はありません。実際のペイロードで、1 日の再送上限が許す回数までテストできます。
- 転送先をトンネルの URL に切り替える
- Dashboard → Events を開き、任意のイベントで Replay をクリックする
- または API を使う:
POST /api/v1/events/{id}/replay - 元のペイロードがバイト単位でそのまま、現在の転送先に送られる
これは特に、エッジケースのデバッグに役立ちます。本番環境のログで問題のイベントを見つけ、ローカルに再送し、ハンドラーが正しく動くまで繰り返します。
保存時の暗号化
Webhook のペイロードはすべて、データベースに届く前に AES-256-GCM で暗号化して保存されます。対象には、受信したヘッダー、リクエストボディ、転送先ごとの署名シークレットが含まれます。
暗号化には、データベースの認証情報とは別に保管している専用の鍵(AES_SECRET_KEY)を使います。暗号化の鍵なしにデータベースが侵害されても、得られるのは暗号文だけです。平文の Webhook データも、署名シークレットも、顧客情報も得られません。
暗号化される情報
- Webhook リクエストのヘッダーとボディ(すべてのイベント)
- プロバイダーの署名シークレット(Stripe の Webhook シークレットなど)
- 転送先ごとの AnyHook 署名シークレット
エッジでの署名検証
AnyHook は、イベントがキューに入る前に、Cloudflare のエッジで Webhook の署名を検証します。App に署名シークレットを設定した後は、署名がない、または無効なリクエストは 401 で拒否され、クォータを消費することはありません。署名シークレットがない場合、リクエストは検証されずに転送されます。
署名方式はヘッダーから自動で判別します。App を作成して送信元の種類を選ぶと、AnyHook は署名シークレットを暗号化して保存し、そのプロバイダーに合ったアルゴリズムを使います。
未対応の送信元やカスタムの送信元
Webhook の送信元が対応リストにない場合は、App の作成時に Generic(汎用)を選びます。AnyHook は次のように動作します。
- 受信したすべての POST を受け付けて、キューに入れます。受信時の署名検証はありません。
- ボディをバイト単位でそのまま、プロバイダーの元のヘッダーとともに転送先へ転送します。配信試行のたびに、新しい AnyHook-Signature も付けます。エッジの手前で追加されるインフラのヘッダー(cf-* など)は取り除きます。
AnyHook-Signatureヘッダーを付けるので、エンドポイントはリクエストが AnyHook を経由したことを検証できます。
Generic の App では、どのシークレットを設定しても、受信時の署名検証は行われません。AnyHook-Signature が証明するのはリクエストが AnyHook を経由したことで、誰が AnyHook に送ったかではありません。送信者の確認が必要なら、送信元の元のヘッダー(そのまま転送されます)の検証を自分で続けてください。送信元が Standard Webhooks(webhook-id、webhook-timestamp、webhook-signature。OpenAI や Replicate が採用)で署名している場合は、代わりにその送信元を選んでください。エッジで検証されます。
AnyHook-Signature
転送されるリクエストには、すべて AnyHook-Signature ヘッダーが付きます。これは、転送先の署名シークレットで生成した、タイムスタンプ付きの HMAC-SHA256 署名です。
AnyHook-Signature: t=1712934567,v1=5257a869...
サーバーでは次のように検証します。
// Node.js / TypeScript
import crypto from "crypto";
function verifyAnyHook(header: string, body: string, secret: string): boolean {
const [tPart, vPart] = header.split(",");
const timestamp = Number(tPart?.replace("t=", ""));
// Re-signed on every attempt, so a genuine signature is never older than this.
if (!timestamp || Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const signature = Buffer.from(vPart?.replace("v1=", "") ?? "", "hex");
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${body}`)
.digest();
// timingSafeEqual throws on a length mismatch, so check that first.
return signature.length === expected.length && crypto.timingSafeEqual(signature, expected);
}署名シークレットは転送先ごとに生成され、ダッシュボードの App → Destinations → Signing secret で確認できます。API でいつでもローテーションできます。
カスタムヘッダー
外向きのリクエストには、転送先ごとにカスタムヘッダーを付けられます。主な用途は次のとおりです。
Authorization: Bearer ...:API での認証x-api-key: ...:API キーによる認証- エンドポイントが必要とする任意のヘッダー
カスタムヘッダーの値は暗号化して保存され、イベントログに表示されることはありません。App → Destinations → Custom headers で設定します。
Stripe-Signature など)は、カスタムヘッダーや AnyHook-Signature と一緒に、バイト単位でそのまま転送されます。そのため、既存の SDK による検証は初回の配信で通ります。リトライと再送にはプロバイダーの元のタイムスタンプが付いており、Stripe の SDK はデフォルトで 5 分を過ぎたものを拒否します。代わりに、配信試行のたびに新しく署名される AnyHook-Signature を検証してください。配信制御
転送先ごとに任意の設定が 3 つあり、いずれも Pro 以上で使えます。App → Destinations → Delivery controls で設定するか、PATCH /api/v1/apps/{slug} で転送先オブジェクトに指定します。Free のアカウントでも、有料プランだったときに設定した制御はそのまま残りますが、変更はできません。
{
"destinations": [{
"url": "https://api.example.com/hooks/stripe",
"event_types": ["invoice.*", "checkout.session.completed"],
"rate_limit": 20,
"delay_seconds": 5
}]
}イベントタイプフィルター
event_types はパターンのリストです。転送先は、タイプがいずれかのパターンに一致するイベントだけを受け取ります。* は任意の文字列に一致するので、invoice.* は invoice のすべてのイベント、checkout.session.completed はその 1 種類だけです。照合はタイプ全体に対して行い、大文字と小文字を区別しません。転送先を 2 つにすれば、タイプごとに振り分けられます。請求関連のイベントはあるサービスへ、それ以外は別のサービスへ、という具合です。
- 判定はエッジで、署名検証の後、クォータのカウントの前に行います。フィルターで除外されたイベントは保存も配信もされず、カウントもされません。
- 送信元には
200 {"filtered":true,"event_type":"…"}が返ります。Stripe と Shopify では、この応答がそれぞれの Webhook ログに表示されます。 - タイプをどこから読むかは、プロバイダーごとに異なります。GitHub、Shopify、WooCommerce、Lemon Squeezy はヘッダー、Stripe、Clerk、Resend、Vercel と Svix 形式の送信元はボディの
type、Paddle と PayPal はevent_typeです。イベントログの Type 列に表示される値と同じです。
type フィールドのない汎用のボディや、プロバイダーの ping)は、フィルターにかかわらず、すべての転送先に配信されます。中継サービスとしての約束は、何も失わないことです。不明なイベントを捨てるフィルターは、その約束を破ることになります。レート制限
rate_limit は、転送先への配信を 1 秒あたりその件数までに制限します(1〜1,000)。上限を超えたイベントはキューで待機し、空きが出しだい順番に送られます。何も破棄されず、リトライも同じ上限に含まれます。送信元がサーバーの処理能力を超える勢いでまとめて送ってくる場合に使います。たとえば、Shopify の商品の一括更新、月初に集中する Stripe のサブスクリプション更新、多数の CI イベントに広がる GitHub のモノレポへの push などです。
一括再送もこの上限に従います。上限がいちばん役立つのは、まさにこの場面です。失敗した 500 件のイベントを 1 秒あたり 20 件で再送すれば、再び失敗する一度きりの急増ではなく、25 秒間の安定したトラフィックになります。
遅延配信
delay_seconds は、最初の配信試行を遅らせます(1 秒〜1 日)。よくある理由は、送信元の Webhook が自身の API と競合する場合です。Stripe の checkout.session.completed は、成功ページが注文を書き込み終える前に届くことがあります。5 秒遅らせれば、競合は問題にならなくなります。再送が遅延されることはなく、最初の配信試行の後のリトライスケジュールも変わりません。
Slack/Discord アラート
Pro 以上では、Settings → Notifications で Incoming Webhook を 1 回設定するだけで、AnyHook がメールで送るアラートがすべてそこにも投稿されます。対象は、配信の失敗(1 回目と 5 回連続)、サーキットブレーカーの作動と無効化、拒否の急増とその解消、そして 50% / 90% / 100% のクォータ通知です。URL を保存するとテストメッセージが投稿されるので、届いたことを確認できます。Slack、Discord、Mattermost、Rocket.Chat、Teams の Incoming Webhook コネクターのいずれでも使えます。ペイロードは {"text": …}(Discord では content)です。「Email on failure」をオフにして止まるのはメールだけで、チャンネルにはアラートが届き続けます。
コードハンドラー
Pro と Scale では、App が受信するすべてのイベントで、自分のコードをサンドボックス内で実行できます。自前のサーバーは必要ありません。コードを保存すると、そのコードを実行する転送先が App に追加されます。そのため、リトライ、再送、サーキットブレーカー、イベントログはほかの転送先と同じように動作し、各実行のコンソール出力と戻り値がその配信のレスポンスになります。コードは App の Code セクションか、以下のエンドポイントで書きます。よくある処理のテンプレートは /code-templates.md にあり、Pipedream からの移行には専用のガイドがあります。
export default async function (event, { env, store }) {
// event: { method, headers, body, raw_body, delivery_id, attempt }
console.log(event.body);
}- イベント。
bodyは、パース済みの JSON、フォームのフィールド(urlencoded、またはマルチパートフォームのテキストフィールド)、あるいは生のテキストです。raw_bodyは受信したままのテキストです。methodは常に POST で、クエリ文字列、パス、呼び出し元の IP は保持されません。delivery_idはイベントのX-AnyHook-Deliveryの ID で、attemptは 1 から数え、テスト実行では 0 です。 - 失敗。例外を投げると配信は失敗になり、プランの配信試行回数の範囲でリトライされます(リトライスケジュール)。実行が成功したイベントのリトライでは、コードは再実行されません。一方、外部への呼び出しが通った後で失敗した実行は再実行されるため、API が対応していれば、そうした呼び出しには
delivery_idを冪等キーとして使ってください。 - Pipedream のステップ。
export default defineComponent({ async run({ steps, $ }) {} })もそのまま動きます。steps.trigger.eventは上記のイベントで、$.exportと$.flow.exitも使えます。data_storeの prop はすべて App のストアになります。app prop の$auth.fieldはシークレットAPP_FIELDを、それ以外の prop は同じ名前のシークレットを読み込みます(channelIdならCHANNEL_ID)。引き継げないものは Pipedream のガイドに載っています。 - npm パッケージ。コードが import するパッケージは、保存時にインストールされます。バージョンは Pipedream と同じ書き方で固定でき(
import axios from "axios@1.7.9")、最大 20 個までです。新しいパッケージをインストールする保存には、数分かかることがあります。インストールに失敗した場合は、何も保存されません。 - シークレット。
env.NAMEまたはprocess.env.NAMEで読み取ります。最大 20 個まで設定できます。名前には英字、数字、アンダースコアを使い、数字で始めることはできません。PATH, HOME, NODE_OPTIONS, NODE_PATH, PWD と、ANYHOOK_で始まる名前はランタイムが使用します。値は最大 4,000 文字です。値がサーバーの外に出ることはありません。 - ストア。
store.get、set({ ttl }に秒数を指定)、has、delete、keys、entries、clearで、イベント間で状態を保持できます。ストアは App ごとに 1 つで、キーは最大 1,000 個、各キーは最大 256 文字、各値は JSON で最大 64,000 バイトです。インポートは{"entries": {key: value}}の形式で受け付け、既存のキーを上書きし、TTL なしで設定します。 - スケジュール。
scheduleは 5 つのフィールドからなる cron 式で、CRON_TZ=Area/Cityを前に付けない限り UTC です。スケジュールが起動するたびに、anyhook.scheduleイベントが App 自身の URL に投稿されます。そのため実行 1 回とイベント 1 件としてカウントされ、起動するのはコードがオンの間だけです。App の送信元は generic か auto で、署名シークレットなしにする必要があります。スケジュールで動かすコードには、専用の App を用意してください。 - メール。どの App も
{user-slug}.{app-slug}@anyhook.netでメールを受信でき、email.receivedイベントになります。ボディにはfrom、to、subject、message_id、date、sizeと、MIME メッセージ全体のrawが入ります。スケジュールと同じく、送信元が generic か auto で、署名シークレットのない App が必要です。プランのペイロード上限を超えるメールはバウンスされます。 - Web ページとフォーム。ページから受信 URL に
fetch()できます。エッジが CORS のプリフライトに応答し、すべての応答にAccess-Control-Allow-Origin: *が付きます。通常の HTML フォームにはmethod="post"が必要です。action に?redirect=https://your-site.com/thanksを付けると、イベントを保存した後、303で訪問者をそのアドレスに移動させます。ただし、そのアドレスがフォームを送信したサイトと同じサイトにある場合に限ります。そうでない場合、訪問者には「Received」という短いページが表示されます。テキストフィールドはevent.bodyに入りますが、アップロードされたファイルは残りません。 - 上限。コードは最大 64,000 バイトで、実行時間は Pro で 105 秒、Scale で 285 秒までです。各実行では、コンソール出力の末尾 16,000 文字が保持されます。月間の実行回数はプランの表にあり、リトライ、テスト実行、再送、スケジュールの起動を含めて、すべての実行がカウントされます。超過料金はありません。月間の実行回数を超えると、コードへの配信は
429を返して保留(circuit_open)になり、リトライはされません。翌月になるかプランを上げれば、再送できます。 - テストとオン/オフ。
POST .../code/testは、App に保存された最新のイベント、指定したevent_id、または渡したbodyで保存済みのコードを実行し、ログ、戻り値、所要時間を返します。実行 1 回としてカウントされ、何も配信しませんが、コード自身が送信するものは実際に送信されます。イベントがコードを実行するのは、コードがオンの間だけです。ダッシュボードは、新しいコードをオフの状態で保存します。一方、PUT .../codeは、"enabled": falseを渡さない限り、最初のバージョンをオンの状態で保存します。その後の保存では、現在の状態が維持されます。移行中のように、別の転送先がまだ同じイベントを処理している間は、"enabled": falseで保存し、その転送先を削除してからコードをオンにしてください。どちらの変更も、すべてのエッジロケーションに反映されるまで最大 1 分かかります。
認証
API リクエストにはすべて、Authorization ヘッダーに Bearer トークンが必要です。
Authorization: Bearer ahk_live_xxxxxxxxxxxxxxxx
API キーは Dashboard → Settings → API Keys で発行します。キーはアカウント単位で、フルアクセスか読み取り専用かを作成時に選べます。キーはいつでも失効させられます。MCP のログイン(anyhook.net/mcp)で接続したアプリは、承認したアクセス権の範囲内で、OAuth アクセストークンを使って同じエンドポイントを呼び出せます。
Retry-After ヘッダー付きの 429 Too Many Requests が返ります。AI エージェント(MCP)
AnyHook は https://anyhook.net/mcp で動く MCP サーバーです。エージェントが代わりに App の作成、イベントログの読み取り、失敗した配信の再送を行えます。Pro と Scale では、App のコードのデプロイとテストもできます(anyhook_code_deploy、anyhook_code_test、anyhook_code_get)。Claude Code、claude.ai、Codex、VS Code は URL とブラウザーでのログインだけで接続でき、キーをコピーする必要はありません。クライアントごとの手順を参照してください。
Cursor、Windsurf、Claude Desktop ではキーを使います。Dashboard → Settings → API Keys でキーを作成すると、キーが入った状態で次の設定が表示されます。
{
"mcpServers": {
"anyhook": {
"command": "npx",
"args": [
"-y",
"anyhook-mcp"
],
"env": {
"ANYHOOK_API_KEY": "ahk_live_xxxxxxxxxxxxxxxx"
}
}
}
}Cursor:Settings → MCP → Add new MCP server、または ~/.cursor/mcp.json。Windsurf:~/.codeium/windsurf/mcp_config.json。Claude Desktop:Settings → Developer → Edit Config。コードのツールを使うには、anyhook-mcp 0.4.0 以降(npx -y で取得されます)か、https://anyhook.net/mcp のサーバーが必要です。
そのあと、エージェントに「Stripe の Webhook 用に AnyHook の App を作成して、その URL を教えてください。」と頼みます。エージェントは、この内容をすべて /llms.txt で読めます。
App
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 }
]
}Stripe または GitHub を接続
Stripe のキーか GitHub のトークンを渡すと、AnyHook は App の受信 URL をプロバイダーに Webhook として追加し、発行された署名シークレットを保存して、最初のイベントから署名を検証します。プロバイダーはキーから判別します。キーはこれらの呼び出しにだけ使い、保存しません。app を指定しないと、新しい App が作成されます。もう一度実行すると、2 つ目を追加するのではなく、既存の登録を置き換えます。
必要なのは最小限の権限だけです。Stripe なら Webhook Endpoints: Write を付けた制限付きキー、GitHub ならリポジトリに対して Webhooks: Read and write を付けた fine-grained トークンです。
# Stripe: a restricted key, connecting an existing app
curl -X POST https://anyhook.net/api/v1/connect \
-H "Authorization: Bearer {api_key}" \
-H "Content-Type: application/json" \
-d '{ "key": "rk_live_...", "app": "payments" }'
# GitHub: name the repository (or an organization); no app, so one is created
curl -X POST https://anyhook.net/api/v1/connect \
-H "Authorization: Bearer {api_key}" \
-H "Content-Type: application/json" \
-d '{ "key": "github_pat_...", "target": "acme/api" }'GitHub はフックが作成されるとすぐに署名付きの ping を送るので、最初のイベントは数秒以内に表示されます。その他のプロバイダーでは、POST /api/v1/apps で App を作成し、その受信 URL をプロバイダーの Webhook 設定に貼り付けます。
イベント
一覧取得のクエリパラメーターは次のとおりです。
| パラメーター | 型 | 説明 |
|---|---|---|
| app_slug | string | App の slug で絞り込み |
| status | string | ステータスで絞り込み(queued、success、retrying、failed、rejected、circuit_open、quota_exceeded) |
| limit | integer | 最大件数(デフォルト 50、最大 200) |
再送(API)
詳細と例は、上の API からの再送を参照してください。
未配信(API)
詳細と例は、上の未配信のイベントを参照してください。
一括再送(API)
詳細と例は、上の一括再送を参照してください。
プランの上限
| Free | Pro($20/月) | Scale($90/月) | Enterprise | |
|---|---|---|---|---|
| イベント数/月 | 10,000 | 50,000 | 250,000 | 柔軟に対応 |
| 1 日の上限 | 1,000 | なし | なし | なし |
| App 数 | 1 | 10 | 50 | 柔軟に対応 |
| App ごとの転送先数 | 3 | 20 | 50 | 柔軟に対応 |
| 配信試行回数 | 3 | 5 | 10 | 柔軟に対応 |
| 保存期間 | 3 日 | 1 年 | 7 年 | 柔軟に対応 |
| 再送可能期間 | 3 日 | 30 日 | 90 日 | 最大 365 日 |
| 配信タイムアウト | 60 秒 | 120 秒 | 5 分 | 柔軟に対応 |
| 最大ペイロード | 512 KB | 2 MB | 4 MB | カスタム |
| 超過料金 | なし | 1k 件あたり $0.15 | 1k 件あたり $0.17 | 契約による |
| 1 日の再送上限 | 100 | 500 | 1,000 | 無制限 |
| コード実行回数/月 | なし | 3,000 | 12,000 | 柔軟に対応 |
| コードの実行時間 | なし | 105 秒 | 285 秒 | 柔軟に対応 |
| コンピュート枠 | $0.15/月 | $3.00/月 | $11.00/月 | 柔軟に対応 |
| API と MCP の呼び出し | 300 回/日 | 1,000 回/日 | 3,000 回/日 | 柔軟に対応 |
使用量とクォータ
AnyHook は使用量を暦月単位(例:「2026-04」)で集計します。カウンターは毎月の初めにリセットされます。
- 配信 1 回につき 1 件:2 つの転送先に送られたイベントは 2 件と数えます。大きなイベントは、ボディとヘッダーの 64 KB ごと(端数は切り上げ)に 1 件と数えます。転送先のない App のイベントは 1 件です。フィルターで除外されたイベントと、署名検証に失敗したイベントは数えません。Free の 1 日の上限はもっと単純で、エッジが受け付けたリクエストを、転送先の数にかかわらず 1 件と数えます。
- 初回の配信試行のみ:リトライはクォータにカウントされません。
- 再送はカウントされません:
is_replay: trueのイベントは課金の対象外です。ただし、クォータ超過で保留(held)になったイベントの再送は例外で、新しいイベントと同じく 1 回カウントされます。再送はコンピュート枠を消費し、プランの 1 日の再送上限にもカウントされます。 - コンピュート枠:各イベントの初回の配信試行を超えて、AnyHook がアカウントのために使う費用を原価で差し引く枠です。対象は、リトライ、再送、コードの実行、パッケージのインストール、スケジュールの起動、障害訓練、そして転送先の応答を最初の 1 秒を超えて待つ時間です(1 KB のイベントのリトライで約 $0.00002、数秒のコード実行で約 $0.0008)。枠を使い切ると、翌月 1 日(UTC)までこれらは止まります。失敗した配信はリトライされず、再送とコードの実行は拒否され、スケジュールは一時停止し、転送先の応答は 1 秒しか待ちません。イベントは引き続き届き、初回の配信試行も行われます。
- API と MCP の呼び出し:API キーまたは MCP クライアントによる呼び出しは、1 回につき 1 単位と数えます。32 KB を超えるレスポンスは、32 KB ごと(端数は切り上げ)に 1 単位と数えます。ダッシュボードからの操作は数えません。1 日の上限を超えると、API は 00:00 UTC まで
Retry-After付きで 429 を返します。GET /api/v1/usage(MCP ではanyhook_usage)で、すべての上限とその使用量を確認できます。 - 破棄せずに保留:プランに含まれるイベント数を超えると(超過料金の上限を設定している場合は、その上限も超えると)、各イベントはステータス
quota_exceededで記録され、配信されません。アップグレードするか上限を設定すれば、ダッシュボードからワンクリックで再送できます。保留にできるのは、上限で購入できる分を超えてクォータの 20% までです。それを超えると、暴走した送信元への歯止めとして、エッジは 429 で拒否し、何も保存しません。
quota_exceeded で表示され、アップグレード後に再送できます。それを超えた分は、1 日の上限と同じく拒否されます。従量制の超過料金を使うには、Pro にアップグレードしてください。超過料金
超過料金を有効にしておけば、月間クォータを使い切ってもイベントは保留されず、流れ続けます。Pro と Scale プランでのみ使えます。
仕組み
- Settings(設定)で、月ごとの超過料金の上限を設定します($5 / $10 / $20 / $50、または Off)。
- プランのイベントクォータに達すると、AnyHook は超過料金が有効になっているか、上限に達していないかを確認します。
- 許可されていれば、イベントは通常どおり配信され、超過分として記録されます。
- 翌月の初めに、超過分は Stripe の請求書に単一の明細として請求されます。Pro は 1,000 件あたり $0.15、Scale は 1,000 件あたり $0.17 です(イベント単位で計算し、セント単位で切り上げ)。請求額は設定した上限までです。
App ごとの切り替え
App にはそれぞれ超過料金の設定があり、Inherit default(デフォルトを継承)、Enabled(有効)、Disabled(無効)から選べます。デフォルトでは、App はアカウント単位の設定を引き継ぎます。App ごとに変更するには、App Settings → Overage Billing を使います。
アカウントのデフォルト
Settings → Overage Billing で Off 以外の超過料金の上限を設定すると、Inherit default に設定されたすべての App で超過料金が有効になります。既存の App も対象です。Disabled に設定した App は、オフのままです。
quota_exceeded として保存され、上限を引き上げるか月が切り替われば再送できます。上限を超えた分が請求されることはありません。上限を超えたイベントは、さらにクォータの 20% まで保留になります。それを超えた分は、エッジが 429 で拒否し、何も保存しません。API キー
API キーは、AnyHook の REST API へのリクエストを認証します。Dashboard → Settings → API Keys で管理します。
- 本番環境用のキーには、
ahk_live_というプレフィックスが付きます。 - 複数のキーを作成し、ラベル(例:「CI/CD」「Agent」)を付けられます。
- キーはすぐに失効させられます。失効したキーは、次に使われたときに
401を返します。 - キーは作成時に 1 回だけ表示されます。安全な場所に保管してください。
お困りですか?
hello@anyhook.net にメールしてください。すべてのメッセージに返信します。サービスの稼働状況は status.anyhook.net で確認できます。