Zendesk Webhook署名の検証方法:完全な開発者向けガイド

Stevia Putri
Written by

Stevia Putri

Reviewed by

Stanley Nicholas

Last edited 2026 3月 2

Expert Verified

Zendesk Webhook署名の検証方法:完全な開発者向けガイドのバナー画像

ZendeskからWebhookを受信する統合を構築する場合、インターネットからのHTTPリクエストを受け入れるエンドポイントをサーバー上に開きます。適切な検証がないと、誰でも偽のリクエストをそのエンドポイントに送信し、システム内で不要なアクションをトリガーする可能性があります。そこで、署名検証が重要になります。

Zendesk Webhook署名検証を使用すると、受信Webhookが実際にZendeskから送信されたものであり、転送中に改ざんされていないことを暗号的に証明できます。このガイドでは、正しく実装するために必要なすべての手順を、5つの一般的なプログラミング言語での動作するコード例とともに説明します。

ZendeskでのWebhookの設定に関するより広範なガイダンスをお探しの場合は、ZendeskメッセージングWebhook設定ガイドで完全な構成プロセスについて説明しています。

eesel AIシミュレーションダッシュボード。Zendesk ChatGPT統合の予測自動化率を示しています
eesel AIシミュレーションダッシュボード。Zendesk ChatGPT統合の予測自動化率を示しています

Webhook署名検証とは何か、そしてなぜ重要なのか

Webhook署名検証は、受信Webhookリクエストの信頼性を確認できるセキュリティメカニズムです。ZendeskがWebhookをエンドポイントに送信すると、Zendeskのみが生成できた暗号署名が含まれます。サーバーは、共有シークレットを使用してその署名を再計算し、結果を比較します。一致する場合、Webhookは本物です。

この検証がないと、エンドポイントはいくつかの攻撃に対して脆弱になります。

  • スプーフィング(Spoofing): WebhookのURLを発見した人は誰でも、Zendeskを装って偽のリクエストを送信できます。
  • リプレイ攻撃(Replay attacks): 攻撃者は正当なWebhookをキャプチャし、複数回再送信できます。
  • ペイロードの改ざん(Payload tampering): リクエストデータは、検出されずに転送中に変更される可能性があります。

機密性の高いチケットデータを処理したり、自動化されたワークフローをトリガーしたりする本番環境での統合では、署名検証はオプションではありません。システムと顧客データの両方を保護する基本的なセキュリティ制御です。

eesel AIでは、Zendeskアカウントを接続すると、Webhookセキュリティが自動的に処理されます。当社のプラットフォームは署名を透過的に検証するため、暗号実装ではなく自動化の構築に集中できます。

Zendesk Webhook署名の仕組み

Zendeskは、SHA256 HMAC(Hash-based Message Authentication Code:ハッシュベースのメッセージ認証コード)アルゴリズムを使用してWebhook署名を生成します。このプロセスでは、Webhookの署名シークレットとリクエストペイロードおよびタイムスタンプを組み合わせて、各リクエストの一意の署名を作成します。

数式は次のようになります。

base64(HMACSHA256(TIMESTAMP + BODY))

ZendeskがWebhookを送信するときに何が起こるかは次のとおりです。

  1. Zendeskは、タイムスタンプと生のリクエストボディを連結して、単一の文字列にします。
  2. Webhookの署名シークレットをキーとして使用して、HMAC-SHA256ハッシュを作成します。
  3. ハッシュはBase64エンコードされ、最終的な署名が生成されます。
  4. Zendeskは、2つの重要なヘッダーを含むWebhookを送信します。
    • X-Zendesk-Webhook-Signature - 生成された署名
    • X-Zendesk-Webhook-Signature-Timestamp - 署名で使用されたタイムスタンプ

ZendeskからのすべてのWebhookリクエストには、次の標準ヘッダーが含まれています。

x-zendesk-account-id: 123456
x-zendesk-webhook-id: 01F1KRFQ6BG29CNWFR60NK5FNY
x-zendesk-webhook-invocation-id: 8350205582
x-zendesk-webhook-signature: EiqWE3SXTPQpPulBV6OSuuGziIishZNc1VwNZYqZrHU=
x-zendesk-webhook-signature-timestamp: 2021-03-25T05:09:27Z

サーバーでこれらのヘッダーを抽出し、保存されている署名シークレットを使用して署名を再計算し、結果を比較します。署名が一致する場合、WebhookがZendeskから送信されたものであり、ペイロードが変更されていないことを信頼できます。

ZendeskからサーバーへのWebhook署名検証フロー
ZendeskからサーバーへのWebhook署名検証フロー

Webhook署名シークレットの取得

署名を検証する前に、Webhookの署名シークレットが必要です。Zendeskの各Webhookには、Webhookの作成時に生成される独自のシークレットがあります。

管理センターでシークレットを見つける

  1. Zendesk管理センター(管理センター > アプリと連携機能 > Webhook)に移動します。
  2. 検証するWebhookを選択します。
  3. Webhookの詳細ページで、署名シークレットフィールドを探します。
  4. 「シークレットを表示」をクリックして、値を表示します。

接続方法を示すZendesk Webhook構成インターフェイス
接続方法を示すZendesk Webhook構成インターフェイス

このシークレットは、他の資格情報と同様に扱ってください。コードにコミットしたり、クライアント側のアプリケーションで公開したりしないでください。また、チーム内でのアクセスを制限してください。

API経由で取得する

Webhook署名シークレットの表示APIを使用して、プログラムで署名シークレットを取得することもできます。

GET /api/v2/webhooks/{webhook_id}/signing_secret

開発用の静的テストシークレット

ZendeskでWebhookを作成する前にWebhookをテストする場合は、実際のシークレットはWebhookの作成後にのみ生成されるため、静的な署名シークレットが必要になります。開発中は、次のテストシークレットを使用してください。

dGhpc19zZWNyZXRfaXNfZm9yX3Rlc3Rpbmdfb25seQ==

Webhookが作成されたら、実際の署名シークレットに切り替えます。テストWebhookとライブWebhookは異なるシークレットを使用するため、検証コードは各環境の正しいシークレットを処理する必要があります。

Zendeskランディングページのスクリーンショット
Zendeskランディングページのスクリーンショット

ステップバイステップの実装ガイド

署名検証の実装には、4つの主要なステップがあります。各ステップを分解してみましょう。

ステップ1:生のリクエストボディをキャプチャする

署名は、解析されたJSONやフォームデータではなく、文字列としての生のリクエストボディで計算されます。フレームワークがアクセスする前にボディを解析する場合、生のバイトが変換されているため、署名検証は失敗します。

ほとんどのWebフレームワークは、解析前に生のボディをキャプチャするためのミドルウェアまたは構成オプションを提供しています。通常、署名計算に使用できるように、生のボディをreq.rawBodyのようなプロパティに格納する必要があります。

よくある落とし穴:ボディ解析ミドルウェア(Expressのexpress.json()など)は、多くの場合、ルートハンドラーの前に実行されます。生の文字列をキャプチャする前にボディがJavaScriptオブジェクトに解析される場合、署名検証のために元のバイトを復元することはできません。最初に生のボディをキャプチャするようにミドルウェアを構成してください。

ステップ2:署名ヘッダーを抽出する

受信リクエストから、署名に関連する2つのヘッダーを取得します。

  • X-Zendesk-Webhook-Signature - 検証する署名
  • X-Zendesk-Webhook-Signature-Timestamp - 署名計算で使用されたタイムスタンプ

一部のフレームワークはヘッダー名を変換することに注意してください。たとえば、Ruby on Railsでは、ヘッダーX-Zendesk-Webhook-Signatureは、リクエスト環境でHTTP_X_ZENDESK_WEBHOOK_SIGNATUREになります。

ステップ3:予期される署名を計算する

タイムスタンプと生のボディを連結し、署名シークレットを使用してHMAC-SHA256ハッシュを作成します。

  1. 文字列を作成します:timestamp + body(最初にタイムスタンプ、次に生のボディ)
  2. 署名シークレットをキーとして使用して、HMAC-SHA256を生成します。
  3. 結果のハッシュをBase64エンコードします。

この計算された署名は、ZendeskがX-Zendesk-Webhook-Signatureヘッダーで送信した署名と一致する必要があります。

ステップ4:署名を安全に比較する

一定時間比較関数を使用して署名を比較します。通常の文字列比較(==または===)は、タイミング分析を通じて署名に関する情報を漏洩させる可能性があり、理論的には攻撃者が有効な署名を偽造するのに役立つ可能性があります。

ほとんどの言語は、一定時間比較関数を提供しています。

  • Node.js:crypto.timingSafeEqual()
  • Python:hmac.compare_digest()
  • PHP:hash_equals()
  • Ruby:ActiveSupport::SecurityUtils.secure_compare()
  • C#:組み込みの一定時間比較はありませんが、.NET CoreのCryptographicOperations.FixedTimeEquals()があります。

署名が一致する場合は、Webhookを処理します。一致しない場合は、401 Unauthorizedレスポンスを返し、調査のために失敗をログに記録します。

一般的な言語でのコード例

最も一般的なWeb開発言語の完全な動作する実装を次に示します。

Node.js/Express

const express = require('express');
const crypto = require('crypto');

const SIGNING_SECRET = 'your_webhook_signing_secret_here';
const PORT = 3000;
const app = express();

// Middleware to capture raw body
function storeRawBody(req, res, buf) {
  if (buf && buf.length) {
    req.rawBody = buf.toString('utf8');
  }
}

app.use(express.json({ verify: storeRawBody }));
app.use(express.urlencoded({ verify: storeRawBody, extended: true }));

function isValidSignature(signature, body, timestamp) {
  const hmac = crypto.createHmac('sha256', SIGNING_SECRET);
  const sig = hmac.update(timestamp + body).digest('base64');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(sig)
  );
}

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-zendesk-webhook-signature'];
  const timestamp = req.headers['x-zendesk-webhook-signature-timestamp'];
  const body = req.rawBody;

  if (!isValidSignature(signature, body, timestamp)) {
    console.log('Invalid webhook signature');
    return res.status(401).send('Unauthorized');
  }

  // Process the verified webhook
  console.log('Webhook verified, processing...');
  res.status(200).send('OK');
});

app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

Python (Flask)

from flask import Flask, request, abort
import hmac
import hashlib
import base64

app = Flask(__name__)
SIGNING_SECRET = b'your_webhook_signing_secret_here'

@app.route('/webhook', methods=['POST'])
def handle_webhook():
    # Get raw body
    raw_body = request.get_data()

    # Extract headers
    signature = request.headers.get('X-Zendesk-Webhook-Signature', '')
    timestamp = request.headers.get('X-Zendesk-Webhook-Signature-Timestamp', '')

    # Calculate expected signature
    signed_payload = (timestamp + raw_body.decode('utf-8')).encode('utf-8')
    expected_signature = base64.b64encode(
        hmac.new(SIGNING_SECRET, signed_payload, hashlib.sha256).digest()
    ).decode('utf-8')

    # Verify signature
    if not hmac.compare_digest(expected_signature, signature):
        abort(401)

    # Process verified webhook
    return '', 200

if __name__ == '__main__':
    app.run(port=3000)

PHP

<?php

define('SIGNING_SECRET', 'your_webhook_signing_secret_here');

function verify_webhook($body, $signature, $timestamp) {
    // Concatenate timestamp and body
    $signed_payload = $timestamp . $body;

    // Calculate HMAC (binary output)
    $calculated_hmac = base64_encode(
        hash_hmac('sha256', $signed_payload, SIGNING_SECRET, true)
    );

    // Constant-time comparison
    return hash_equals($signature, $calculated_hmac);
}

// Handle webhook request
$signature = $_SERVER['HTTP_X_ZENDESK_WEBHOOK_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_ZENDESK_WEBHOOK_SIGNATURE_TIMESTAMP'] ?? '';
$body = file_get_contents('php://input');

if (!verify_webhook($body, $signature, $timestamp)) {
    http_response_code(401);
    exit('Unauthorized');
}

// Process verified webhook
http_response_code(200);
echo 'OK';

Ruby on Rails

class WebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token

  SIGNING_SECRET = ENV['ZENDESK_WEBHOOK_SECRET']

  def zendesk
    signature = request.headers['HTTP_X_ZENDESK_WEBHOOK_SIGNATURE']
    timestamp = request.headers['HTTP_X_ZENDESK_WEBHOOK_SIGNATURE_TIMESTAMP']
    body = request.body.read

    # Calculate signature
    signed_payload = timestamp + body
    expected_signature = Base64.strict_encode64(
      OpenSSL::HMAC.digest('SHA256', SIGNING_SECRET, signed_payload)
    )

    # Verify
    unless ActiveSupport::SecurityUtils.secure_compare(expected_signature, signature)
      head :unauthorized
      return
    end

    # Process webhook
    head :ok
  end
end

C#

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("webhook")]
public class WebhookController : ControllerBase
{
    private const string SigningSecret = "your_webhook_signing_secret_here";

    [HttpPost]
    public IActionResult HandleWebhook()
    {
        string signature = Request.Headers["X-Zendesk-Webhook-Signature"];
        string timestamp = Request.Headers["X-Zendesk-Webhook-Signature-Timestamp"];

        // Read raw body
        using var reader = new StreamReader(Request.Body);
        string body = reader.ReadToEnd();

        // Calculate signature
        string signedPayload = timestamp + body;
        byte[] keyBytes = Encoding.UTF8.GetBytes(SigningSecret);
        byte[] payloadBytes = Encoding.UTF8.GetBytes(signedPayload);

        using var hmac = new HMACSHA256(keyBytes);
        byte[] hash = hmac.ComputeHash(payloadBytes);
        string expectedSignature = Convert.ToBase64String(hash);

        // Compare (case-insensitive for compatibility)
        if (!signature.Equals(expectedSignature, StringComparison.OrdinalIgnoreCase))
        {
            return Unauthorized();
        }

        return Ok();
    }
}

一般的な問題とトラブルシューティング

正しいコードを使用しても、署名検証は微妙な理由で失敗する可能性があります。開発者が遭遇する最も一般的な問題を次に示します。

JSONのスペーシングとフォーマット

最もイライラする問題の1つは、JSONのフォーマットに関係しています。署名は、Zendeskが送信する正確なバイト(空白を含む)で計算されます。フレームワークがJSONを再フォーマットする(スペースを追加または削除する)場合、署名は一致しません。

Zendeskコミュニティの開発者は、これを痛いほど思い知りました。

Zendesk Community
解決しました。原因は、jsonリクエストのスペーシングにありました。jsonペイロードには、':'の前後に1つのスペースが必要で、他の場所にはスペースやタブは不要です。

解決策は、解析または変換が発生する前に、常に生のリクエストボディに対して署名を検証することです。

テストWebhookとライブWebhookの違い

もう1つの一般的な問題は、ZendeskのテストWebhook機能とライブWebhookの呼び出しの違いに関係しています。ペイロードの形式は2つでわずかに異なる場合があり、テストでは署名が検証されますが、本番環境では失敗します。

Zendesk Community
Webhookが正常に実行されると、メッセージ認証が失敗することに気付きました。そこで、失敗した試行からボディをコピーし、「Webhookをテスト」に戻り、コンテンツをメッセージボディに貼り付けて送信したところ、メッセージの検証が再び機能するようになりました。

本番環境にデプロイする前に、実際のZendeskイベントからの実際のWebhook呼び出しで常にテストしてください。

文字エンコーディング

署名計算のために連結するときは、タイムスタンプとボディの両方がUTF-8文字列として処理されるようにしてください。サーバーとZendeskのペイロード間のエンコーディングの不一致は、検証の失敗を引き起こします。

タイムスタンプの検証

リプレイ攻撃を防ぐために、タイムスタンプの検証を追加することを検討してください。ヘッダーのタイムスタンプが、現在の時刻から妥当な範囲内(たとえば、5分以内)であることを確認してください。古いタイムスタンプは、リプレイ攻撃を示している可能性があります。

シークレットを再生成するタイミング

署名シークレットが侵害された疑いがある場合は、Zendesk管理センターからすぐに再生成してください。再生成後、新しいシークレットでサーバーを更新します。飛行中のWebhookが古いシークレットを使用する短い期間がある可能性があるため、移行中は両方をサポートすることを検討してください。

Webhook検証のテスト

本番環境にデプロイする前に、署名検証の実装を徹底的にテストしてください。

静的テストシークレットの使用

開発中は、Zendeskの静的テストシークレット(dGhpc19zZWNyZXRfaXNfZm9yX3Rlc3Rpbmdfb25seQ==)を使用して、実装ロジックが正しいことを確認します。これにより、ライブWebhookを作成せずにテストできます。

Zendeskのテスト機能を使用したテスト

ZendeskでWebhookを作成または編集するときは、「Webhookをテスト」ボタンを使用してテストペイロードを送信します。エンドポイントがこれらのリクエストを受け入れ、署名を正しく検証することを確認してください。

ライブテスト

特定のイベント(チケットの作成など)でWebhookを呼び出す実際のトリガーを作成します。Zendeskでそのアクションを実行し、エンドポイントがWebhookを受信して検証することを確認します。サーバーログで署名の不一致がないか確認してください。

ロギングとデバッグ

開発中に次の情報をログに記録して、失敗のデバッグに役立ててください。

  • 生のリクエストボディ(解析前)
  • 受信した署名ヘッダー
  • 計算された署名
  • タイムスタンプヘッダー

署名シークレット自体は絶対にログに記録しないでください。受信した署名と計算された署名を文字ごとに比較して、どこが異なるかを特定します。

Webhook署名検証の体系的なテストワークフロー
Webhook署名検証の体系的なテストワークフロー

eesel AIでZendesk統合を保護する

Webhook署名検証を正しく実装するには、暗号の詳細、フレームワーク固有のボディ処理、およびJSONフォーマットに関するエッジケースに注意を払う必要があります。複雑な統合を構築するチームにとって、この複雑さは開発を遅らせ、セキュリティリスクをもたらす可能性があります。

eesel AIでは、Zendesk統合にWebhookセキュリティを直接組み込んでいます。Zendeskアカウントを当社のプラットフォームに接続すると、署名検証が自動的に処理されます。検証コードを作成および保守することなく、セキュリティ上のメリットが得られます。

ノーコードインターフェイスでスーパーバイザーエージェントを構成するためのeesel AIダッシュボード
ノーコードインターフェイスでスーパーバイザーエージェントを構成するためのeesel AIダッシュボード

Zendesk用AIエージェントはさらに進んで、すべてのWebhookセキュリティをバックグラウンドで処理しながら、自律的なチケット解決を提供します。Webhookベースの自動化を構築していて、暗号実装ではなくビジネスロジックに集中したい場合は、統合を簡素化するお手伝いをします。

よくある質問

はい、Webhook署名シークレットを表示または再生成するには、Zendesk管理センターへの管理者アクセス権が必要です。シークレットはデフォルトで非表示になっており、表示するには「シークレットを表示」をクリックする必要があります。
いいえ、各Webhookには、作成時に生成される独自の署名シークレットがあります。Webhook間でシークレットを共有することはできず、あるWebhookのペイロードを別のWebhookのシークレットで検証しようとすると、常に失敗します。
まず、正しい署名シークレットを使用していることを確認します(ライブWebhook用の静的なテストシークレットではありません)。JSON解析の前に、生のリクエストボディをキャプチャしていることを確認してください。タイムスタンプとボディの連結がZendeskの形式と正確に一致していることを確認してください。最後に、ペイロードにエンコードの問題がないことを確認してください。
厳密には必須ではありませんが、署名検証は、本番環境での統合には強く推奨されます。署名検証がないと、エンドポイントはスプーフィング攻撃やリプレイ攻撃に対して脆弱になります。ZendeskはすべてのWebhookに署名ヘッダーを含めているため、検証することにデメリットはありません。
侵害が疑われる場合、アクセス権を持つチームメンバーが退職した場合、または定期的なセキュリティ監査の一環として(四半期ごとまたは年1回)、署名シークレットをローテーションしてください。シークレットをローテーションすると、新しいWebhookが新しいシークレットを使用するため、検証コードをすぐに更新する必要があることに注意してください。
いいえ、静的なテストシークレットは、Webhook作成中のZendeskの「Webhookをテスト」機能でのみ機能します。Webhookが作成されると、独自の署名シークレットが生成され、検証に使用する必要があります。
HMAC-SHA256ハッシュとBase64エンコードをサポートする言語であれば、Zendesk Webhookを検証できます。最も一般的な実装では、Node.js、Python、PHP、Ruby、Java、C#、Goが使用されます。暗号化操作は標準であり、ほとんどの言語の標準ライブラリで利用できます。

この記事を共有

Stevia undefined

Article by

Stevia Putri

Stevia Putri is a marketing generalist at eesel AI, where she helps turn powerful AI tools into stories that resonate. She’s driven by curiosity, clarity, and the human side of technology.