DrupalでWebhookを使用する

[Drupal Advent Calendar 2023](https://qiita.com/advent-calendar/2023/drupal) の2日目です。

システム連携で「Webhook」はよく使われる手段だと思うのですが、Drupal界隈ではWebhook関連の記事を見かけたことがないなとふと気づきました。

本記事では「Drupalが送信する側」であることを前提に、DrupalでWebhookを実現するにはどんな選択肢があるのか、それぞれの方法の特徴を考察していきたいと思います。

## DrupalでWebhookが使えると何が嬉しいのか

例えば、「Drupalで新しいコンテンツが作成された」ようなイベントをトリガーに、

* 外部システムにコンテンツを同期する
* Slackに通知する

といったことが実現できるように、主に外部システムとの連携で役立ちます。

Webhookを使用しなくても、DrupalコアのJSON:APIを一定の頻度でポーリングすれば同じようなことができますが、APIのポーリングは、イベントの頻度が低ければ低いほど非効率なんですよね。いつイベントがトリガーになるか分からないけどリアルタイムで拾いたい時はWebhook欲しいなと思います。逆にイベントの頻度が高ければポーリングで過不足ないのかなと思ったりします。

## DrupalでWebhookを利用するには

前提として、Drupalコアにはそのような機能は存在しません。Google先生に聞いたところ、2つの方法が出てきました。

* Zapierを使用する前提 -> Zapierで用意されたDrupalインテグレーションを使用する
* コントリビュートモジュールで実現する -> [Webhooksモジュール](https://www.drupal.org/project/webhooks)を使用する

## パターン1: Zapierで用意されたDrupalインテグレーションを使用する

Zapierには様々なサービスがトリガーやアクションとして利用できるのですが、任意のDrupalサイトと接続するためのインテグレーションが用意されていました。

https://zapier.com/apps/drupal/integrations

### ZapierでDrupalインテグレーションを使用する

#### Drupal側の設定

1. Drupalサイトに管理者としてログインします。管理メニューの Extend に移動して、「JSON:API」と「HTTP Basic Authentication」にチェックを入れてモジュールを有効化します。Zapierとの連携をする上で必要なモジュールになります。
1. 余力があれば、Zapier用のユーザーアカウントを作成しておきます。ユーザー権限はなんでも良いです。
![Screen Shot 2023-12-01 at 17.59.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/6fcfdbdf-5137-71be-e25e-f481e52f1caf.png)

#### Zapier側の設定

1. Zapierにログインして、左側の [Create Zap] をクリックします。
1. 画面上の [1. Trigger] をクリックします。
![Screen Shot 2023-12-01 at 17.46.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/af671bf9-fb82-8b1b-281d-bb6fa48baf49.png)
1. 検索窓に「drupal」と入力して、一覧に出てきた [Drupal] をクリックします。
![Screen Shot 2023-12-01 at 17.46.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/de5b82aa-ed5d-8e2e-ab95-e232b2a94f1f.png)
1. 右側に設定項目が表示されます。まずは「App & event」の設定です。「Event」に [New Content] と指定して、[Continue] をクリックします。
![Screen Shot 2023-12-01 at 17.53.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/180d130f-c413-d786-91d0-12d141c158fa.png)
1. 次に「Account」の設定です。「Connect Drupal」の横にある [Sign in] をクリックします。
![Screen Shot 2023-12-01 at 17.54.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/ecff24f9-1583-71b2-f3d6-0d959250a0fb.png)
1. 接続したいDrupalサイトのURLと、ユーザー名&パスワードを入力して [Yes, Continue to Drupal] をクリックします。接続に成功するとウィンドウが自動的に閉じます。
![Screen Shot 2023-12-01 at 18.00.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/36ff8c06-7d10-bb31-15dc-2e96028f3630.png)
1. 次に「Trigger」の設定です。「Contenttype」に [Article] を指定して、[Continue] をクリックします。
![Screen Shot 2023-12-01 at 18.06.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/3d25bb75-1fe5-283f-6841-b0cf027fbb23.png)
1. 最後に「Test」で接続テストを行います。[Test trigger] をクリックすると、Drupalとの接続確認が行われ、既存のコンテンツをいくつか取得してZapierでどんな値が扱えるのかを確認できます。[Continue with selected record] をクリックします。これでTriggerの設定は完了です。
![Screen Shot 2023-12-01 at 18.08.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/0161ca3c-1d9b-3636-d161-89e12055cd13.png)
1. 左側の画面の [2. Action] をクリックします。イベントが発火した時に何を実行したいかなので、今回は分かりやすく「Gmail」を選択してメールを送るようにしたいと思います。
1. 「App & event」項目で、「Event」に [Send Email] を選択して [Continue] をクリックします。
![Screen Shot 2023-12-01 at 18.13.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/40282abf-7cfc-c723-4dad-e4f0ef57324a.png)
1. 「Account」項目で、接続したいGmailアカウントと接続設定を行います。[Continue] で次に進みます。
1. 「Action」項目で、「To」に送信先メールアドレスを指定します。「Subject」にメール件名を入力します。「Boby」にメール本文を入力します。最後に [Continue] をクリックします。
![Screen Shot 2023-12-01 at 18.17.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/ee8c17fa-9af8-34d1-ed17-58c8722c3de8.png)
1. 「Test」で [Test Step] をクリックして、実際にメールが送信されるか確認します。
![Screen Shot 2023-12-01 at 18.20.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/e1f0abf9-b494-0f00-5080-06dc2b0ce164.png)
1. 最後に [Publish] をクリックして、作成したZapが動くようにします。

### Webhookを動かしてみる

1. Drupalサイトでコンテンツを作成して公開します。
1. 約15分後にメール通知が届きました。
1. Zapierのダッシュボードにある「Zap history」からも実行された形跡を確認できました。

### 所感

* **イベントの対象が限定的:** Zapierの設定で、トリガーイベントとして選択できるのが「New content」のみでした。このコンテンツはいわゆるノードなので、タクソノミータームやユーザーなどは対象外です。またCRUD操作のうち、可能なのはCreateのみでUpdateやDeleteといったイベントはトリガーしてくれないので、使用場面が限定的になるように感じました。
* **Webhook・・・？:** Drupalサイトのapache logを確認すると、JSON:APIを定期的にポーリングしているだけのように見えます。それはそのはずで、前述の通りDrupalコアにWebhookのようなイベントが発火した時に送信する仕組みは提供されていないので、APIをポーリングする以外に方法が無いのです。

パターン1の方法は、実務的に片手落ち感があることが分かりました。またZapier以外のWebhookレシーバーを使用する場合には利用できません。

## パターン2: Webhooksモジュールを使用する

Webhooksモジュールは、Googleで「Webhooks Drupal」で検索した時に一番上に出てくるコントリビュートモジュールです。

![Screen Shot 2023-12-01 at 11.48.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/f2d3328f-86bd-347a-39df-ada5700f47ca.png)

※2番目の検索結果に出てくるWebhookモジュールはDrupal 7バージョンのみに対応しているものなので、Drupal 10では利用できません。

### Webhooksモジュールの概要

モジュールのプロジェクトページを眺めて得られる情報として、利用数は多くないのと、alpha版しか出ていないのは少し気になりますが、Drupal 10対応していることと、直近もコードが更新されているのでアクティブなプロジェクトであることが伺えます。

Drupalのイベントに対してWebhookを送信することと、Drupal側で外部のWebhookを受信することの両方が可能だと説明されています。

### Webhooksモジュールを使用する

WebhookレシーバーとしてZapierを使いたいところですが、Zapierでは任意のWebhookを受け取るにはプランアップ(=課金)をしないといけないので、今回は無料で利用できる[pipedream](https://pipedream.com/)というサービスを利用します。

#### pipedream側の設定

1. プロジェクトが1つもない場合、まず初めに右上の [New project] をクリックして適当なプロジェクトを作成します。プロジェクトが無いとワークフローが作成できません。
![Screen Shot 2023-12-01 at 16.43.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/947a93fd-0f2e-1d67-876d-a8463d147e62.png)
1. 右上の [New workflow] をクリックしてワークフローを1つ作成します。設定項目が色々ありますが、全てデフォルトのままで問題無しです。
![Screen Shot 2023-12-01 at 16.45.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/901aabf6-4b04-f097-59be-6959b0adcc93.png)
1. トリガーを設定する画面が表示されるので、[New HTTP / Webhook Requests] をクリックします。
![Screen Shot 2023-12-01 at 16.47.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/11879a92-9336-c608-4163-4936300a3017.png)
1. 次の画面は全てデフォルトのまま、[Save and continue] をクリックします。
![Screen Shot 2023-12-01 at 16.48.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/fc61134c-9743-823a-84b9-4eeaca72b674.png)
1. トリガーの設定が完了するとURLが発行されます。Drupalでイベントが発火したら、DrupalがこのURLにリクエストを送信してくれます。このURLは後ほどDrupal側の設定で利用するのでコピーしておきます。
![Screen Shot 2023-12-01 at 16.49.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/58eb8f20-fd4b-eef5-02b6-01a670dd4b30.png)
1. 画面の下にある [+] をクリックして、イベントが発火した後に何を実行するかを設定します。今回はWebhooksモジュールの挙動がわかれば十分なので、[Run custom code] をクリックします。
![Screen Shot 2023-12-01 at 16.56.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/b5e45a89-be4d-19b9-dd89-e3703963f65f.png)
1. エラーが起こらなければ内容はなんでも良いです。これで設定は以上になるので右上の [Deploy] をクリックします。
![Screen Shot 2023-12-01 at 16.58.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/f7ee64f2-b760-72cc-95da-8c86d4d9f601.png)

#### Drupal側の設定

1. Composerでモジュールをインストールします。
   ```shell
   $ composer require 'drupal/webhooks:^3.0@alpha'
   ```
1. 「Webhooks」モジュールを有効化します。
   ```
   $ drush pm:enable webhooks
   ```
   「Webhook」というモジュールも同梱されていますが、これは「Webhook」というコンテンツエンティティが提供されるもので、今回の調査では不要です。
1. 管理メニューの Configuration > Web services > Webhooks (URL: /admin/config/services/webhook)に移動して、[Add Webhook] をクリックします。
![Screen Shot 2023-12-01 at 17.02.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/4f3f5f85-0608-d888-8dfe-960f40306b50.png)
1. 以下の各項目を入力します。
   * Label: `First Webhook` <-なんでも良い。
   * Type: `Outgoing` <- つまり「送信」ですね。incomingが「受信」。
   * Outgoing Webhook Settings:
      * Payload URL: `https://xxxxx.m.pipedream.net` <- pipedream側の設定の手順5で生成されたURLを入力。
      * Hook / Eventは `Hook: Content entity:node:create` にチェックを入れる。これでコンテンツが作成された時に発火するようになる。
![Screen Shot 2023-12-01 at 17.03.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/371d6d2d-d808-975f-b19a-9248c977a3cc.png)
![Screen Shot 2023-12-01 at 17.04.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/9655ae39-a0a9-42e8-1967-2704ec0404fc.png)
1. 他はそのままにして [Save] をクリックします。これで設定完了。

### Webhookを動かしてみる

1. Drupalサイトでコンテンツを作成して公開します。
![Screen Shot 2023-12-01 at 17.10.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/c82c0786-362a-eb14-843b-8830c3c1b2f1.png)
1. pipedream側の管理画面にアクセスして、対象のワークフローが実行されたことを確認します。下画像の赤枠部分をクリックすると、右側のウィンドウに実行内容が詳細に表示されます。
![Screen Shot 2023-12-01 at 17.15.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/1a173a0b-6951-79a4-c0e1-d191a0789b60.png)
1. ワークフローの各タスクにある[Exports]タブのbodyを開くと、Drupalからどんな値を渡されたのかを確認できます。手順1で作成した「This is Test article」という記事の情報が渡っていることが分かります。
![Screen Shot 2023-12-01 at 17.17.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/aec16640-b2a4-ec38-f3f8-d4db7adea48e.png)
1. Drupal側のログも確認してみます。管理メニューの Reports > Recent log messages に移動します。ログ一覧に「Webhook Dispatched. ...」というログがありますが、Webhookが送信されたことがログからも確認できました。
![Screen Shot 2023-12-01 at 17.26.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79577/44e3fac9-d674-2b69-a078-c46639154a6f.png)

### 所感

* **様々なイベントに対応:** Drupalのコンテンツエンティティに対するCRUD操作をイベントとして発火させることができるようです。
* **ドキュメントがない:** Webhooksモジュールの[Readme.md](https://git.drupalcode.org/project/webhooks/-/blob/3.0.x/README.md?ref_type=heads)にはインストール方法しか書いてありませんでした。難しい設定は特にないので困ることはないのですが、同梱されているWebhook(マシン名: webhook)モジュールがどんな時に役立つのかは謎のままです。
* **安定版がない&利用数が少ない:** 2011年に公開されているので、古株の部類にはなるとは思うのですが。

Webhooksモジュールは管理画面もきちんと用意されており、特につまづくポイントもなく直感的に操作できました。サブエンティティタイプなどでイベントの絞り込みができるような機能があると良いように感じましたが、現状のままでも機能面では過不足はないです。だからこそ利用数は伸びてほしい代物だと感じました。

## まとめ

DrupalでWebhookを利用する方法をまとめてみました。Drupalにおいて、システム連携は頻繁に発生するシーンであることから、Webhookを利用することは有益な選択肢の1つです。今回紹介した2つの方法にはそれぞれ一長一短があり、DrupalでWebhookを取り扱うことがマイナーであることを強く感じました。今後事例が増えることを期待しています。