Webhook を
本記事の
内容は 2026 年 9 月時点の 仕様 (バージョン 1.0.0)と 各社の 公開情報です。 出典は 末尾に まとめています。
Standard Webhooks の概要
Standard Webhooks は、
運営は
仕様が
3 つのヘッダー
Standard Webhooks に
| ヘッダー | 内容 | 例 |
|---|---|---|
webhook-id | イベントごとの | msg_2KWPBgLlAfxdpx2AI54pPJ85f4W |
webhook-timestamp | 送信を | 1674087231 |
webhook-signature | 署名。 | v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4= |
webhook-id はwebhook-timestamp は
署名の計算方法
署名にはv1)とv1a)の
対称鍵(v1)
送信側と
webhook-id、webhook-timestamp、本文を ピリオドで つないだ文字列を 作る ( msg_xxx.1674087231.{"type":"order.paid",...})- 鍵で
HMAC-SHA256 を 計算し、 結果を base64 に する - 先頭に
v1,を付けて webhook-signatureに入れる
鍵はwhsec_ をwhsec_ を
非対称鍵(v1a)
Ed25519 のwhsk_ でwhpk_ で
受信側の確認事項
仕様は、
- 時刻の
許容範囲 :webhook-timestampが現在時刻から 一定の 範囲内かを 確かめ、 過去の リクエストの 再利用 (リプレイ攻撃)を 防ぎます。 公式ライブラリの JavaScript 版は 前後 5 分を 範囲と しています - 重複の
排除 :webhook-idを冪等キーと して 記録し、 同じ イベントを 2 回処理しないようにします。 再送や ネットワークの 都合で、 同じ イベントが 複数回届く ことが ある ためです - 一定
時間での :対称鍵の比較 署名は、 比較に かかる 時間が 内容で 変わらない 関数で 比べます。 通常の 文字列比較では、 タイミング攻撃の 対象に なります - 受け取ったままの
本文 :本文をJSON と して 読み込んでから 文字列に 戻すと、 空白や 並び順が 変わって 署名が 一致しなくなります。 検証には 受け取った バイト列を そのまま 使います
鍵の切り替え
webhook-signature に
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4= v1,PGOol9edfeMgYBaSh+rI9ThEyc0IoEXMU2Xteb3Pl68=
公式ライブラリ
署名の
- Python
(PyPI の standardwebhooks) - JavaScript/TypeScript
(npm の standardwebhooks) - Java/Kotlin
(Maven Central の com.standardwebhooks:standardwebhooks) - Rust
(crates.io の standardwebhooks) - Go、Ruby、PHP、C#、Elixir
この
署名検証のコード例(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:Webhook が
Standard Webhooks の 仕様に 沿っており、 公式ライブラリで 検証できると 明記しています - Supabase:認証の
HTTP フックが Standard Webhooks の 仕様を 実装しており、 検証の 例に standardwebhooksを使っています - Anthropic:Claude Managed Agents の
Webhook が webhook-id・webhook-timestamp・webhook-signatureの3 つの ヘッダーと whsec_で始まる 鍵を 使い、 5 分を 超えた 配信は SDK の 検証で 拒否されます
複数の
送信側の実装
当社のv1)のwhsec_ で
出典
- 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 の署名はどの言語で検証できますか?
公式の
署名検証が失敗する原因で多いものは何ですか?
受け取った
独自形式の署名から Standard Webhooks に移行できますか?
仕様では、