ZendeskのWebhook機能について学ぶ – Zendesk入門 Advent Calendar 2021 Day17 #Zendesk

イベント発生時、指定したREST APIにリクエストを送る機能
2021.12.17

AWS事業本部オペレーション部 サービスグロースチームの加藤です。

はじめに

このエントリは クラスメソッド Zendesk入門 Advent Calendar 2021の17日目の記事です。

ZendeskとWebhook

Zendesk Supportでは、トリガ/自動化などのイベントが発生した際に
指定したURLにHTTPリクエストを送信することができます。

Slack等サードパーティのWebhookや自前のREST APIも利用することができるので、Zendesk外の各種システムとの連携や自動化の自由度が高くなります。

余談: 最近までZendeskからREST APIにリクエストを送りたいときは 「HTTPターゲット」という機能が使われていましたが、HTTPターゲットは2022年2月に提供終了となり、今回紹介するWebhook機能に一本化されるようです。

HTTPターゲットの提供終了とWebhookへの変換に関するお知らせ – Zendeskヘルプ

設定画面

Webhookの設定画面は、管理センター> アプリ及びインテグレーション> Webhook の中にあります。
作成画面は以下のようになっています。

エンドポイントURL

リクエストを送信するURLを指定します。

リクエスト方法

5つのHTTPメソッドに対応しています。

  • GET
  • POST
  • PUT
  • PATCH
  • DELETE

リクエスト形式

  • JSON
  • XML
  • エンコードフォーム

認証

HTTPSを利用する場合に限り、認証に必要な機能が利用可能です。利用する場合は2種類から選択できます。

  • Basic認証
  • ベアラートークン

webhookをテスト

やってみました。

(下準備)Webhookの作成


API Gateway + Lambda でAPIを作成し、API GatewayのエンドポイントURLに対してリクエストを送信するZendeskのWebhookを作成してみます。

実用性を重視するならば

  • 緊急度の高いチケットが作成されたときにSlackに通知
  • ユーザのチケット入力に応じて回答を生成し、Zendesk APIを利用して自動で返信を行う
  • チケットの起票者がZendesk外の顧客管理システムに登録されている場合、登録内容をZendeskの社内メモとして書き込む

などのような機能があるといいなと思いますが、
今回はお試しなので、WebhookからAPI Gatewayをキックした際に裏側のLambda(Lambdaプロキシ統合を利用)に届いたレスポンスをほぼまるごと出力してみます。

今回はAPI Gateway + Lambdaの作成に Serverless Frameworkを利用しました。
使ったコードはServerless Frameworkのテンプレートほぼそのままです。

serverless.yml

service: webhook-test

frameworkVersion: '2'
provider:
  name: aws
  runtime: python3.8
  lambdaHashingVersion: '20201221'

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: /
          method: get

handler.py

import json

def hello(event, context):
    body = {
        "message": "Kato Test: Request successfully!",
        "input": event,
    }

    response = {"statusCode": 200, "body": json.dumps(body)}

    return response

上記をデプロイし、作成されたAPI GatewayのエンドポイントURLを控えておきます。

Webhookをテスト


Webhook URLと各種設定を指定して、 「Webhookをテスト」をクリックすると画面右側にWebhook送信テスト画面が表示されます。

リクエストURLパラメータを指定した上で「送信テスト」をクリックします。

リクエストに成功し、レスポンスの内容が表示されました。

今回はZendesk WebhookからAPI Gatewayをキックした際に裏側のLambda(Lambdaプロキシ統合を利用)に届いたリクエストをinput: 以下にまるごと表示しています。

一部伏せていますが、以下のようにリクエストのUser-Agentは "Zendesk Webhook" となったり、Zendesk独自のリクエストヘッダが付与されたりしているようです。
また、指定したリクエストURLパラメータはqueryStringParameters、またはmultiValueQueryStringParametersの中に入っているようです。

{
    "message": "Kato Test: Request successfully!",
    "input": {
        "resource": "/",
        "path": "/",
        "httpMethod": "GET",
        "headers": {
            "Accept-Encoding": "gzip",
            "CloudFront-Forwarded-Proto": "https",
            "CloudFront-Is-Desktop-Viewer": "true",
            "CloudFront-Is-Mobile-Viewer": "false",
            "CloudFront-Is-SmartTV-Viewer": "false",
            "CloudFront-Is-Tablet-Viewer": "false",
            "CloudFront-Viewer-Country": "JP",
            "Content-Type": "application/json; charset=utf-8",
            "Host": "<伏せ>.execute-api.us-east-1.amazonaws.com",
            "User-Agent": "Zendesk Webhook",
            "Via": "1.1 <伏せ>.cloudfront.net (CloudFront)",
            "X-Amz-Cf-Id": "<伏せ>",
            "X-Amzn-Trace-Id": "<伏せ>",
            "X-Forwarded-For": "<伏せ>",
            "X-Forwarded-Port": "443",
            "X-Forwarded-Proto": "https",
            "X-Request-Id": "76e4cc3e-01dc-96eb-bc29-1ddf681fbedd",
            "X-Zendesk-Account-Id": "<伏せ>",
            "X-Zendesk-Webhook-Id": "test_webhook:fake_webhook:<伏せ>",
            "X-Zendesk-Webhook-Invocation-Id": "test_webhook:fake_event:<伏せ>",
            "X-Zendesk-Webhook-Signature": "<伏せ>",
            "X-Zendesk-Webhook-Signature-Timestamp": "2021-12-16T12:24:21Z"
        },
        "multiValueHeaders": {
            "Accept-Encoding": [
                "gzip"
            ],
            "CloudFront-Forwarded-Proto": [
                "https"
            ],
            "CloudFront-Is-Desktop-Viewer": [
                "true"
            ],
            "CloudFront-Is-Mobile-Viewer": [
                "false"
            ],
            "CloudFront-Is-SmartTV-Viewer": [
                "false"
            ],
            "CloudFront-Is-Tablet-Viewer": [
                "false"
            ],
            "CloudFront-Viewer-Country": [
                "JP"
            ],
            "Content-Type": [
                "application/json; charset=utf-8"
            ],
            "Host": [
                "<伏せ>.execute-api.us-east-1.amazonaws.com"
            ],
            "User-Agent": [
                "Zendesk Webhook"
            ],
            "Via": [
                "1.1 <伏せ>.cloudfront.net (CloudFront)"
            ],
            "X-Amz-Cf-Id": [
                "<伏せ>"
            ],
            "X-Amzn-Trace-Id": [
                "Root=<伏せ>"
            ],
            "X-Forwarded-For": [
                "<伏せ>"
            ],
            "X-Forwarded-Port": [
                "443"
            ],
            "X-Forwarded-Proto": [
                "https"
            ],
            "X-Request-Id": [
                "76e4cc3e-01dc-96eb-bc29-1ddf681fbedd"
            ],
            "X-Zendesk-Account-Id": [
                "<伏せ>"
            ],
            "X-Zendesk-Webhook-Id": [
                "test_webhook:fake_webhook:<伏せ>"
            ],
            "X-Zendesk-Webhook-Invocation-Id": [
                "test_webhook:fake_event:<伏せ>"
            ],
            "X-Zendesk-Webhook-Signature": [
                "<伏せ>"
            ],
            "X-Zendesk-Webhook-Signature-Timestamp": [
                "2021-12-16T12:24:21Z"
            ]
        },
        "queryStringParameters": {
            "name": "pochi",
            "sound": "bowwow"
        },
        "multiValueQueryStringParameters": {
            "name": [
                "pochi"
            ],
            "sound": [
                "bowwow"
            ]
        },
        "pathParameters": null,
        "stageVariables": null,
        "requestContext": {
            "resourceId": "<伏せ>",
            "resourcePath": "/",
            "httpMethod": "GET",
            "extendedRequestId": "<伏せ>",
            "requestTime": "16/Dec/2021:12:24:21 +0000",
            "path": "/dev/",
            "accountId": "<伏せ>",
            "protocol": "HTTP/1.1",
            "stage": "dev",
            "domainPrefix": "<伏せ>",
            "requestTimeEpoch": 1639657461737,
            "requestId": "<伏せ>",
            "identity": {
                "cognitoIdentityPoolId": null,
                "accountId": null,
                "cognitoIdentityId": null,
                "caller": null,
                "sourceIp": "<伏せ>",
                "principalOrgId": null,
                "accessKey": null,
                "cognitoAuthenticationType": null,
                "cognitoAuthenticationProvider": null,
                "userArn": null,
                "userAgent": "Zendesk Webhook",
                "user": null
            },
            "domainName": "<伏せ>.execute-api.us-east-1.amazonaws.com",
            "apiId": "<伏せ>"
        },
        "body": null,
        "isBase64Encoded": false
    }
}

トリガからWebhookを呼び出す

次に、自動化やトリガなどのビジネスルールイベント発生時にWebhookを呼び出す設定を行います。

トリガとそのしくみについて – Zendeskヘルプ
自動化とそのしくみについて – Zendeskヘルプ

今回は次のようなトリガを設定してみました。
特定ブランドに所属するチケットが新規に作成されたとき、チケットタイトルをURLパラメータに追記してWebhookをキックするものです。
(ちなみに、Webhookの「リクエスト方法」がPOSTの場合はリクエストbodyも指定できるようになっていました)

この設定を入れると、Webhook側の画面から、自分がどのトリガ or 自動化処理から呼び出されているかを一覧することができるようになります。

テストチケットを起票してシステムログ表示を見てみると、トリガ経由で「メッセージがWebhookに送信されました」という記載が確認できます。

アクティビティログ

Webhookの設定画面から、呼出履歴を一覧できます。

実行IDをクリックすると、リクエストとレスポンスの詳細を確認することができます。

先ほど作成した「テストチケットです」という名前のチケットがWebhookにリクエストを送信したことがわかります。

信頼性の検証

Webhook受信側でZendeskからのリクエストが本物であることを検証できます。

検証には、先程確認したZendeskからWebhook URLへのリクエストに含まれるHTTPヘッダを用います。 本記事ではドキュメント紹介のみにとどめますが、詳細はこちらをご参照ください。

  • X-Zendesk-Webhook-Signature
  • X-Zendesk-Webhook-Signature-Timestamp

Verifying webhook authenticity | Zendesk Developer Docs

おわりに

今回は検証のため実用性に欠けたサンプルを作りましたが、 実際はZendesk APIを使って自分でプログラムを作ったり、Slackなどサードパーティ製品のWebhookと連携していろいろな自動化を進めることができます。
Zendeskを利用中で、業務に使っている各種システムともっと連携して広く活用してみたい方は、ぜひ活用してみてください。

参考ドキュメント:
管理センターでのWebhookの作成 – Zendeskヘルプ