Webhook を受け取る側は、届いたリクエストが本当に送信元のサービスから来たものかを署名で確かめます。ところが署名の形式はサービスごとに違い、ヘッダー名、署名する文字列、鍵の扱いを連携先ごとに調べて実装してきました。Standard Webhooks は、この署名とヘッダーの形式をそろえるための公開仕様です。ここでは仕様の中身と、受信側での署名検証の実装を整理します。
本記事の内容は 2026 年 9 月時点の仕様(バージョン 1.0.0)と各社の公開情報です。出典は末尾にまとめています。
Standard Webhooks の概要
Standard Webhooks は、Webhook の送信サービスを提供する Svix が中心となってまとめ、2023 年 12 月に公開した仕様です。仕様とライブラリは GitHub で公開されており、仕様は Apache License 2.0 です。
運営は技術運営委員会が担い、2026 年 9 月時点の委員は Zapier、Twilio、Lob、Mux、ngrok、Supabase、Svix、Kong の各社のエンジニアです。
仕様が解決しようとしているのは、Webhook の実装がサービスごとに異なる点です。受信側は連携先が増えるたびに署名の検証方法を調べ直し、送信側は同じ課題をそれぞれ設計してきました。Standard Webhooks は、署名の方式・ヘッダー・本文の形・再送の考え方を 1 つの仕様にまとめています。
3 つのヘッダー
Standard Webhooks に沿った Webhook は、本文と一緒に次の 3 つのヘッダーを送ります。
| ヘッダー | 内容 | 例 |
|---|---|---|
webhook-id | イベントごとの一意な ID。再送しても変わらない | msg_2KWPBgLlAfxdpx2AI54pPJ85f4W |
webhook-timestamp | 送信を試みた時刻(UNIX 時間の秒) | 1674087231 |
webhook-signature | 署名。複数あるときは空白で区切る | v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4= |
webhook-id はイベントに付く ID で、再送のたびに同じ値が届きます。webhook-timestamp は送信を試みた時刻で、再送のたびに新しくなります。
署名の計算方法
署名には対称鍵(v1)と非対称鍵(v1a)の 2 つの方式があります。
対称鍵(v1)
送信側と受信側が同じ鍵を持つ方式で、HMAC-SHA256 を使います。手順は次のとおりです。
webhook-id、webhook-timestamp、本文をピリオドでつないだ文字列を作る(msg_xxx.1674087231.{"type":"order.paid",...})- 鍵で HMAC-SHA256 を計算し、結果を base64 にする
- 先頭に
v1,を付けてwebhook-signatureに入れる
鍵は 24〜64 バイトのランダムな値を base64 にし、先頭に whsec_ を付けた形で受信側に渡します。計算に使うのは whsec_ を除いて base64 から戻したバイト列です。
非対称鍵(v1a)
Ed25519 の鍵ペアを使う方式です。送信側が秘密鍵(whsk_ で始まる)で署名し、受信側は公開鍵(whpk_ で始まる)で検証します。受信側に秘密の値を渡さずに済むため、仕様は非対称鍵を推奨しています。一方で、計算の負荷は対称鍵より高くなります。
受信側の確認事項
仕様は、署名の一致に加えて次の点を確かめるよう求めています。
- 時刻の許容範囲:
webhook-timestampが現在時刻から一定の範囲内かを確かめ、過去のリクエストの再利用(リプレイ攻撃)を防ぎます。公式ライブラリの JavaScript 版は前後 5 分を範囲としています - 重複の排除:
webhook-idを冪等キーとして記録し、同じイベントを 2 回処理しないようにします。再送やネットワークの都合で、同じイベントが複数回届くことがあるためです - 一定時間での比較:対称鍵の署名は、比較にかかる時間が内容で変わらない関数で比べます。通常の文字列比較では、タイミング攻撃の対象になります
- 受け取ったままの本文:本文を JSON として読み込んでから文字列に戻すと、空白や並び順が変わって署名が一致しなくなります。検証には受け取ったバイト列をそのまま使います
鍵の切り替え
webhook-signature に複数の署名を入れられるのは、鍵を止めずに切り替えるためです。送信側は切り替え後の一定期間、新しい鍵と古い鍵の両方で署名し、空白で区切って送ります。受信側はどちらかの署名が一致すれば受け付けるため、この併用期間のうちに新しい鍵へ更新すれば、受信を止めずに切り替えられます。
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4= v1,PGOol9edfeMgYBaSh+rI9ThEyc0IoEXMU2Xteb3Pl68=
公式ライブラリ
署名の作成と検証を行う公式のライブラリが、次の 9 言語で公開されています。
- Python(PyPI の
standardwebhooks) - JavaScript/TypeScript(npm の
standardwebhooks) - Java/Kotlin(Maven Central の
com.standardwebhooks:standardwebhooks) - Rust(crates.io の
standardwebhooks) - Go、Ruby、PHP、C#、Elixir
このほか、Haskell や Swift などのコミュニティによる実装もあります。
署名検証のコード例(Node.js)
公式ライブラリを使う場合
Express で受け取る例です。express.raw() で本文をバイト列のまま受け取り、verify に渡します。署名が一致しない場合や時刻が範囲外の場合は例外になります。
import express from 'express';
import { Webhook } from 'standardwebhooks';
const wh = new Webhook(process.env.WEBHOOK_SECRET); // whsec_ で始まる鍵
const app = express();
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
let event;
try {
event = wh.verify(req.body.toString('utf8'), req.headers);
} catch {
return res.status(400).end();
}
// webhook-id で処理済みかを確かめてから処理する
console.log(req.headers['webhook-id'], event.type);
res.status(204).end();
});
app.listen(3000);
ライブラリを使わない場合
Node.js 標準の crypto だけで書くと次のようになります。時刻の確認、複数の署名への対応、一定時間での比較を含めています。
import crypto from 'node:crypto';
function verify(secret, headers, body, toleranceSec = 300) {
const id = headers['webhook-id'];
const ts = headers['webhook-timestamp'];
const sigHeader = headers['webhook-signature'];
if (!id || !ts || !sigHeader) return false;
if (Math.abs(Math.floor(Date.now() / 1000) - Number(ts)) > toleranceSec) return false;
const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
const expected = crypto.createHmac('sha256', key).update(`${id}.${ts}.${body}`).digest();
return sigHeader.split(' ').some((s) => {
const [ver, sig] = s.split(',');
if (ver !== 'v1' || !sig) return false;
const got = Buffer.from(sig, 'base64');
return got.length === expected.length && crypto.timingSafeEqual(got, expected);
});
}
body には受け取ったままの本文の文字列を渡します。
対応している主なサービス
Standard Webhooks の公式サイトは、対応しているサービスとして OpenAI、Anthropic、Google Gemini、Twilio、Kong、PagerDuty、Supabase などを挙げています。各社の開発者向け資料での扱いは次のとおりです。
- OpenAI:Webhook が Standard Webhooks の仕様に沿っており、公式ライブラリで検証できると明記しています
- Supabase:認証の HTTP フックが Standard Webhooks の仕様を実装しており、検証の例に
standardwebhooksを使っています - Anthropic:Claude Managed Agents の Webhook が
webhook-id・webhook-timestamp・webhook-signatureの 3 つのヘッダーとwhsec_で始まる鍵を使い、5 分を超えた配信は SDK の検証で拒否されます
複数のサービスの Webhook を受け取る場合でも、検証の処理を 1 つにまとめられます。
送信側の実装
当社の Webhook 統合インテグレーション「Webhook Admin」は、Standard Webhooks 形式(v1)の署名を付けて Webhook を送信します。送信先ごとに whsec_ で始まる鍵を発行し、鍵を切り替えた後の 24 時間は新旧両方の鍵で署名します。受信側は本記事のコードや公式ライブラリでそのまま検証できます。再送、配信記録、障害の通知も含めて、送信側の仕組みを自社で作らずに Standard Webhooks に対応できます。
出典
- Standard Webhooks
- Standard Webhooks: 仕様(GitHub)
- Standard Webhooks: リポジトリの README(公式ライブラリ・技術運営委員会)
- Standard Webhooks: JavaScript ライブラリ
- Svix Blog: Announcing Standard Webhooks
- OpenAI: Webhooks
- Supabase: Auth Hooks
- Claude Platform Docs: Subscribe to webhooks
よくある質問
Standard Webhooks の署名はどの言語で検証できますか?
公式のライブラリが Python・JavaScript/TypeScript・Java/Kotlin・Rust・Go・Ruby・PHP・C#・Elixir の 9 言語にあります。Node.js では npm の standardwebhooks を使い、Webhook クラスの verify に受け取った本文とヘッダーを渡します。
署名検証が失敗する原因で多いものは何ですか?
受け取った本文を JSON として読み込み、もう一度文字列に直してから検証することです。空白や並び順が 1 文字でも変わると署名は一致しません。検証には受け取ったままのバイト列を使います。
独自形式の署名から Standard Webhooks に移行できますか?
仕様では、既存のヘッダーを残したまま Standard Webhooks のヘッダーを追加する移行方法を示しています。署名用の鍵も既存のものを使えるため、受信側は準備ができた順に新しい形式の検証へ切り替えられます。