1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Chatworkシリーズ #10】Webhook署名検証を入れたら全メッセージが消えた

1
Last updated at Posted at 2026-03-18

Chatworkのボットが急に黙った。

昨日まで元気に動いていたのに、Webhook署名検証を入れた瞬間、全メッセージが401で弾かれるようになった。ログを見ると1件も通っていない。正直焦った。ボットが止まると業務が止まる。

「セキュリティを強化しよう」と思ったのが裏目に出た話と、そこからどう復旧したかを書く。

なぜ署名検証を入れたのか

ChatworkのWebhookは、デフォルトだとURLさえ知っていれば誰でもリクエストを投げられる。つまり、Webhook URLが漏れた瞬間にボットを踊らせ放題だ。偽メッセージを送り込んで、後続処理を暴走させることもできる。

Chatworkはこの対策として、Webhook送信時にHMAC-SHA256の署名を付けてくれる。ヘッダーに X-ChatWorkWebhookSignature という値が入る。受信側でこの署名を検証すれば、「本当にChatworkから来たリクエストか」を判定できる。

やらない理由がない。やった。全部壊れた。

ハマり1: n8nのHeader Authは使えない

最初に試したのは、n8nのWebhookノードに標準で用意されている「Header Auth」だ。ヘッダーに特定のトークンが入っているかチェックする仕組み。

n8nの設定画面で X-Webhook-Token を指定して、Chatwork側にもそのトークンを……と思ったが、Chatwork側にカスタムヘッダーを追加する設定がない

Chatworkが送ってくるのは X-ChatWorkWebhookSignature という独自ヘッダーで、n8nのHeader Authが期待する X-Webhook-Token ではない。ヘッダー名が違うので永遠にマッチしない。全リクエストが401。

これに気づくまで30分溶けた。ログには「Unauthorized」としか出ないので、トークンの値が間違っているのか、ヘッダー名が違うのか、判断がつかなかった。

教訓: Chatworkの署名検証は、n8nの標準Auth機能では対応できない。Codeノードで自前実装が必要。

ハマり2: rawBodyがないとHMACが合わない

Header Authを諦めて、Codeノードで自前検証に切り替えた。HMAC-SHA256の計算自体は難しくない。

ところが、計算結果が合わない。何度やっても合わない。

原因は「何に対してHMACを計算するか」だった。最初はこう書いていた。

const body = JSON.stringify($input.first().json.body);
hmac.update(body);

これだとn8nがパースしたJSONオブジェクトを再度文字列化している。元のリクエストボディと微妙に違う。キーの順序、空白、エスケープ。どれか一つでも違えばハッシュは別物になる。

必要なのはrawBody——HTTPリクエストの生のボディ文字列だ。

n8nのWebhookノードには「Raw Body」を取得するオプションがある。これをオンにすると、$input.first().json.rawBody でパース前の生文字列が取れる。

Webhookノード → Settings → Raw Body → ON

この設定を入れた瞬間、HMACが一致するようになった。地味な設定だが、これがないと永遠にハッシュが合わない。

ハマり3: トークンはbase64エンコードされている

もう一つ罠がある。Chatworkの管理画面に表示されるWebhookトークンはbase64エンコード済みだ。

最初はそのまま文字列として使っていた。

// NG: 生文字列のままHMACキーにしている
const hmac = crypto.createHmac('sha256', WEBHOOK_TOKEN);

これだと当然ハッシュが合わない。正しくはbase64デコードしてからバイナリキーとして使う。

// OK: base64デコードしてからキーにする
const key = Buffer.from(WEBHOOK_TOKEN, 'base64');
const hmac = crypto.createHmac('sha256', key);

Chatworkの公式ドキュメントにちゃんと書いてある。書いてあるのだが、急いでいると読み飛ばす。

最終的なn8n実装

全部のハマりを潰した後の構成はこうなった。

Webhook受信(rawBody ON)
  ↓
HMAC署名検証(Codeノード)
  ↓
フィルター(自分の投稿を除外等)
  ↓
後続処理(ボット応答・ルーティング等)

Codeノードの中身。

const crypto = require('crypto');

// ヘッダーとrawBodyを取得
const headers = $input.first().json.headers;
const rawBody = $input.first().json.rawBody;

// 署名ヘッダーの存在チェック
const signature = headers['x-chatworkwebhooksignature'];
if (!signature) {
  throw new Error('Missing X-ChatWorkWebhookSignature header');
}

// トークンをbase64デコードしてHMACキーにする
const WEBHOOK_TOKEN = $env.CHATWORK_WEBHOOK_TOKEN; // 環境変数から取得
const key = Buffer.from(WEBHOOK_TOKEN, 'base64');

// HMAC-SHA256でハッシュを計算
const hmac = crypto.createHmac('sha256', key);
hmac.update(rawBody);
const expected = hmac.digest('base64');

// 署名の突き合わせ
if (signature !== expected) {
  throw new Error(`Invalid signature: got ${signature}, expected ${expected}`);
}

// 検証OK → 後続ノードへ
return $input.all();

ポイントを整理する。

項目 正解 やりがちなミス
検証方法 Codeノードで自前実装 n8nのHeader Auth(ヘッダー名が違う)
ボディ rawBody(生文字列) JSON.stringify(再構築された文字列)
トークン base64デコードしてから使う 生文字列のまま使う
トークンの保管 環境変数 ハードコード

フォールバック: rawBodyが取れない場合

環境によってはrawBodyが空になるケースがある。保険としてフォールバックを入れておくと安心。

hmac.update(rawBody || JSON.stringify($input.first().json.body));

ただし、JSON.stringify側にフォールバックすると署名が合わない可能性がある。あくまで「エラーで全部止まるよりマシ」程度の保険だ。本命はrawBodyを確実に取得する設定を入れること。

復旧までの時間

署名検証を入れてボットが全停止してから、原因特定と修正完了まで約1時間。内訳はこんな感じだった。

  • Header Authで詰まる: 30分
  • rawBody問題に気づく: 15分
  • base64デコードに気づく: 10分
  • テスト・動作確認: 5分

正直、30分の時点で「署名検証やめて元に戻そうか」と思った。でもWebhook URLが漏れるリスクを考えると、ここで妥協するのは違う。1時間の投資でボットの安全性が格段に上がったので、結果的にはやってよかった。

まとめ

Chatwork Webhook × n8nで署名検証を入れるときの3つの罠。

  1. n8nのHeader Authは使えない → Codeノードで自前実装
  2. rawBodyが必要 → WebhookノードでRaw Bodyをオンにする
  3. トークンはbase64Buffer.from(token, 'base64') でデコードしてからキーにする

どれも公式ドキュメントを丁寧に読めばわかることだが、「動いていたものに検証を足す」というシチュエーションだと焦りが先に立つ。特にrawBodyの罠はChatworkに限らず、Webhook署名検証では定番のハマりどころなので、n8nでWebhook受けている人は覚えておいて損はない。


Chatworkシリーズ

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?