🌊

セキュアなWebhookのための規格 Standard Webhooksとライブラリを紹介

2024/09/02に公開

とあるOSSのコードを読んでいるときに、Standard Webhooksというライブラリを採用しているのを見つけ、なかなか良さそうな規格 & ライブラリだったので紹介したいと思います。

Standard Webhooksとは

Standard Webhooksは、Webhookを簡単に・安全に・確実に送信するためのオープンソースなツールとガイドラインのセットです。

https://github.com/standard-webhooks/standard-webhooks

Webhookの課題

自分たちでWebhookを実装しようとした時に、Stripe, Slack, Shopifyなどの既存のサービスを参考にしようとドキュメントを読んでみると、意外とばらつきがあるのがわかります。
(全部のサービスを調べるのが面倒なため、以下の表はClaude 3.5 Sonnetに書かせました。正確性は保証できません。が、体感でWebhook実装のバラツキがあるのはわかっていただけると思います)

主要サービスのWebhook実装比較表
サービス イベントタイプ形式 署名方式 署名ヘッダー ペイロード形式 リトライ機能 タイムスタンプ検証
Stripe resource.action (e.g., customer.created) HMAC-SHA256 Stripe-Signature JSON あり (指数バックオフ) あり (許容範囲: 5分)
Shopify resource/action (e.g., orders/create) HMAC-SHA256 X-Shopify-Hmac-SHA256 JSON あり (19時間で最大19回) なし
Slack event_type (e.g., message) HMAC-SHA256 X-Slack-Signature JSON あり (3日間で最大3回) あり (許容範囲: 5分)
GitHub action (e.g., push) HMAC-SHA256 X-Hub-Signature-256 JSON あり (最大8回、24時間) なし
PayPal RESOURCE.ACTION (e.g., PAYMENT.CAPTURE.COMPLETED) RSA-SHA256 PAYPAL-AUTH-ALGO JSON あり (詳細不明) あり (許容範囲: 10分)
Twilio resource.action (e.g., call.completed) HMAC-SHA1 X-Twilio-Signature Form-encoded あり (最大14回、24時間) なし
Dropbox event_type (e.g., file.added) HMAC-SHA256 X-Dropbox-Signature JSON あり (詳細不明) なし
Gitlab object_kind (e.g., push) Secret Token比較 X-Gitlab-Token JSON なし なし
SendGrid event (e.g., delivered) 公開鍵検証 X-Twilio-Email-Event-Webhook-Signature JSON あり (24時間で72回まで) あり (許容範囲: 5分)
Asana event_type (e.g., task_created) HMAC-SHA256 X-Hook-Signature JSON あり (詳細不明) なし

これらをまとめると、以下のようなトピックについて実装の際に検討する必要があることがわかります。

  • 署名方式
  • イベントタイプの命名規則
  • ペイロードのデータ構造
  • タイムスタンプを検証すべきかどうか
  • リトライをどのように設計すべきか

とはいえ、これを毎回サービスを作るごとに考えるのは面倒ですよね。

Standard Webhooksの規格を軽く紹介

Webhookを実装する際に、セキュアに実装するためのガイドラインがStandard Webhooksです。
ガイドライン詳細は以下のリンクを読んでみてください。

https://github.com/standard-webhooks/standard-webhooks/blob/main/spec/standard-webhooks.md

簡単に概要を紹介します。

ペイロード

  • HTTPボディにJSONフォーマットで送信することを推奨。
  • 基本構造:
    • type: イベントタイプ(例:user.created
    • timestamp: イベント発生時刻
    • data: イベントの実際のデータ

セキュリティ

  • 署名スキーム
    • メッセージID、タイムスタンプ、ペイロードを連結して署名。
    • HMAC-SHA256とed25519の両方をサポート。
  • Webhookヘッダー
    • webhook-id: 一意のWebhook識別子
    • webhook-timestamp: Unixタイムスタンプ
    • webhook-signature: 署名

その他

  • イベントタイプ
    • 階層的でピリオドで区切られた識別子を使用(例:user.created
  • 配信の信頼性
    • 複数日にわたる指数バックオフと乱数を用いた再試行スケジュール
    • 一貫した失敗の場合は他のチャネルで通知し、エンドポイントを無効化
    • タイムアウトは15〜30秒を推奨

他にもセキュリティに関するガイドラインや、すでにWebhookを提供している場合の移行戦略についても紹介されています

Standard Webhooksが解決するもの

Standard Webhooksエコシステムが作られることで、以下のような利点があるとしています。

  • APIゲートウェイの署名検証:署名検証は、Webhook利用者にとって共通の課題です。 Standard Webhooksは、検証をAPIゲートウェイに直接実装することを可能にし、消費者の検証を簡単に解決します。
  • 署名と検証のための一連のライブラリを持つことで、APIゲートウェイが使えないシナリオでもウェブフックの検証が容易になります。
  • ワークフロー自動化ツール(Zapier、Make、Workato、tray.ioなど)は、安全な統合を保証し、統合ビルダーが毎回車輪を再発明する必要性を省くために、署名検証を自ら実装することができます。
  • Standard Webhookは、署名の検証に加え、スキーマの検証(JSONスキーマ、OpenAPIまたはAsyncAPI定義を使用)も可能なWebhookコンシューマー用のSDKを自動的に生成するビルドツールを可能にします。

ライブラリの紹介

Python, JavaScript/TypeScript, Java/Kotlin, Rust, Go, Ruby, PHP, C#, Elixir で使えるライブラリが公開されています。

今回はTypescript/Javascriptで利用できるライブラリを紹介します。

https://www.npmjs.com/package/standardwebhooks

このライブラリでは以下の機能が提供されています

  • Webhookペイロードの署名検証、タイムスタンプの検証
  • 署名の生成

実装もシンプルになっているので、中身を読めば何をしているのか簡単に把握できます。

署名検証

受信したWebhookデータを検証するには、verifyメソッドを使用します。

import { Webhook } from './webhook';

// インスタンスの生成
const secret = 'whsec_your_secret_key';
const webhook = new Webhook(secret);

// Webhookで送られてくるデータ
const payload = '{"event": "user.created", "user_id": "123"}';
const headers = {
  'webhook-id': 'msg_1234567890',
  'webhook-timestamp': '1630000000',
  'webhook-signature': 'v1,Nw5XIdZJUcbeo5XAKLfUUNZBOeGyqPvgkiPpKVYVIIQ='
};

// 署名検証
try {
  const verifiedPayload = webhook.verify(payload, headers);
  console.log('検証成功:', verifiedPayload);
} catch (error) {
  console.error('検証失敗:', error.message);
}

verifyメソッドは、ペイロードが正当であればJSONオブジェクトを返し、そうでない場合はWebhookVerificationErrorをスローします。

またverifyメソッドでは、timestampを用いることで、自動的に5分以上前のリクエストや未来の日付のリクエストを拒否する仕様になっています。

署名の生成

クライアントとしてWebhookを送信する場合、signメソッドを使用して署名を生成できます。

const msgId = 'msg_1234567890';
const timestamp = new Date();
const payload = '{"event": "user.created", "user_id": "123"}';

const signature = webhook.sign(msgId, timestamp, payload);
console.log('生成された署名:', signature);

生成されたsignatureをリクエストヘッダーに付与しましょう。

まとめ

Standard Webookのライブラリを使うことで、安全にWebhook通信を実装できます。ペイロードの検証、タイムスタンプの確認、そして署名の生成と検証を簡単に行えるため、弊社でも新規に開発するサービスではセキュアなアプリケーション間通信を行うために積極的に採用して行きたいと思います。


最後に、エンタメ業界でtoC向けサービスを開発するトラストハブではエンジニアメンバーの採用を積極的に行なっております!
ご興味のある方は、ぜひ 私のXアカウント にご連絡ください。カジュアルにお話ししましょう!

TrustHub テックブログ

Discussion