この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
AWS事業本部オペレーション部 サービスグロースチームの加藤です。
はじめに
このエントリは クラスメソッド Zendesk入門 Advent Calendar 2021の17日目の記事です。
ZendeskとWebhook
Zendesk Supportでは、トリガ/自動化などのイベントが発生した際に
指定したURLにHTTPリクエストを送信することができます。
Slack等サードパーティのWebhookや自前のREST APIも利用することができるので、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ヘルプ