Shopify の Webhook を試してみる

Shopify では、外部サービスと連携する仕組みの一環として、多くの Webhook イベントを用意している。カートに商品追加したとか、注文完了とか、支払い完了したとか。これを使って、Shopfiy だけではできない様々なことが行えるようになる。

ここでは、以下２点を紹介する。

- Shopify の Webhook の仕様
- Webhook リクエストをローカルのサービスで動作確認する方法

# Shopify の Webhook について


## Shopify がサポートしている Webhook タイミング

Shopify が提供している Wehook の一覧は、以下のページに書いてある。
https://help.shopify.com/api/reference/webhook

## Shopify の Webhook の仕様

リクエストメソッド: POST
リクエストボディの形式: JSON か XML
タイムアウト: 5 秒
失敗時の繰り返しルール: 最初の失敗から、48時間で、19回繰り返す。(間隔についての記載はないので、不明)
レスポンス: Webhook を受けたら、200 を返す。

詳しくは、ドキュメントの[ここ](https://help.shopify.com/api/getting-started/webhooks)を参照。

### ヘッダー

Shopify の Webhook へのリクエストヘッダーには以下の情報が含まれる。これは、サービス側でのリクエストの検証に使える。

- **X-Shopify-Topic** : どの Webhook イベントか。例: `orders/create`
- **X-Shopify-Hmac-Sha256** : Body を Hmac 化したもの。検証に使う。例: `XWmrwMey6OsLMeiZKwP4FppHH3cmAiiJJAweH5Jo4bM=`
- **X-Shopify-Shop-Domain** : どの Shopify ストアからのものか。例: `johns-apparel.myshopify.com`

### ボディ

ボディの内容は、Webhook の topic によって異なるので、各 Webhook の仕様を確認ください。

## 設定方法

Shopify の Webhook を利用する手順は以下の通り。

事前条件: Shopify ストアにログインできること。

1. Shopify の管理画面にログインし、画面左下の Settings をクリックする。
2. **Notifications** メニュークリックする。
3. 一番下までスクロールすると、**Webhooks** というセクションがある。
4. **Create webhook** ボタンを押し、以下の情報を入力する。
   - **Event** : Webhook 送信タイミング。
   - **Format** : JSON or XML 
   - **URL** : 送信先の URL
5. **Save webhook** をクリックする。

以上。これで、設定したタイミングになったときに、設定した宛先に POST リクエストが飛んで行く。

注： みての通り、Shopify の Webhook では設定で、API キーや認証情報は指定できない作りになっている。そのため、送られてきたリクエストが、本当に Shopify からのものか、途中で改竄されていないかは、サービス側で検証する必要がある。

次に、その検証方法を説明する。

## Webhook リクエストの検証方法

[Shopify ドキュメントにあるまんま](https://help.shopify.com/api/getting-started/webhooks#verify-webhook)を書く。ここでは、Ruby (というか Sinatra) を使った場合の例を示す。ドキュメントには、PHP の場合の例も載っている。

### 検証ロジック

検証は、リクエストヘッダーにある Shopify からの HAMC (`X-Shopify-Hmac-SHA256`) とリクエストボディとシークレットキーを使って、算出した HMAC が同じ値になるかを比較する。

### 検証に使うデータ

- リクエストヘッダーにある `X-Shopify-Hmac-SHA256`
- シークレットキー: シークレットキーは、Shopify 管理画面の Webhook の設定の一番下に以下のメッセージがあるので、そこにある文字列。

```
All your webhooks will be signed with dfdfdfadsfadf2332ad3da09un3krjfdjflsafadfoj32lfadfcfdauf2a1ca4 so you can verify their integrity.
```

### コード

```rb
require 'rubygems'
require 'base64'
require 'openssl'
require 'sinatra'

# The Shopify app's shared secret, viewable from the Partner dashboard

SHARED_SECRET = 'my_shared_secret'

helpers do
  # Compare the computed HMAC digest based on the shared secret and the request contents

  # to the reported HMAC in the headers

  def verify_webhook(data, hmac_header)
    digest  = OpenSSL::Digest::Digest.new('sha256')
    calculated_hmac = Base64.encode64(OpenSSL::HMAC.digest(digest, SHARED_SECRET, data)).strip
    ActiveSupport::SecurityUtils.secure_compare(calculated_hmac, hmac_header)
  end
end

# Respond to HTTP POST requests sent to this web service

post '/' do
  request.body.rewind
  data = request.body.read
  verified = verify_webhook(data, env["HTTP_X_SHOPIFY_HMAC_SHA256"])

  # Output 'true' or 'false'

  puts "Webhook verified: #{verified}"
end
```

※: `my_shared_secret` は上記に書いたシークレットキーで置き換える 

## Webhook リクエストの内容の確認

上記までで、リクエストを検証できる段階になったかと思います。ただ、実際にどんなデータが飛んで来るのか、事前に確認したいかと思います。ここではそれを確認します。

確認するためのツールとして、[RequestBin](https://requestb.in/)という外部サービスを利用します。これは一時的に有効は URL を発行していくれて、そこで受けたリクエストを Web 画面上に表示してくれるサービスです。このような外部リクエストの内容確認用のサービスなのです。

1. [RequestBin](https://requestb.in/)にアクセスします。
2. `private` にチェックし、`Create a RequestBin` を押します。
3. URL が発行されるので、この URL をコピーします。この画面はこのまま閉じずに置いておきます。リクエストが飛んできたらこの画面に内容が表示されるからです。
4. Shopify の管理画面の Webhook の設定 (Settings > Notifications) に行き、URL にこの URL を設定します。
5. 設定後、その設定の右の(ゴミ箱アイコンの左) `send test notification` をクリックします。これで、テストデータで RequestBin 宛にリクエストが送信されました。
6. RequestBin の画面に戻り、画面をリフレッシュします。これでリクエストの内容を確認できたと思います。リクエストヘッダーやボディを確認してください。
7. ストアの準備ができている場合は、Webhook のイベントに設定したタイミングで、実際にどんなデータが飛んで来るのか、確認してください。(例えば、購入完了のタイミングに設定しているのであれば、実際に購入完了をしてみてください。) 

RequestBin は同じ URL で何回もリクエストを受けることができるので、送信したら画面をリフレッシュし、内容を確認してください。


# ローカルで動作確認

上記までで、Shopify の Webhook を受けるエンドポイントを作成できる状態になったと思います。ここでは、必要な内容を実装し終わり、それが正常に動くのか、実際の Shopfiy の Webhook からのリクエストを受けて確認する方法を説明します。

そのサービスをインターネットに公開できていれば、ここで説明する内容は必要ありませんが、普通は先にローカルで確認したいかと思います。

実際に Shopify からの Webhook を受けるためには、サービスがインターネットに公開されていなくてはいけません。ここでは、[**ngrok**](https://ngrok.com/) というトンネルサービスを利用して、ローカルで立ち上げたサーバーを一時的に、インターネットからアクセスできるようにします。

## ngrok の準備

1. [ngrok をダウンロード](https://ngrok.com/)
2. ダウンロードしたディレクトリに移動。
3. OSX または、Linux の場合は、`./ngrok http 4567` で起動。Windows の場合は、`ngrok http 4567`。
4. 起動すると、ngrok が立ち上がり、`Forwarding` ってとこに、外部からアクセス可能な URL が表示される。これが、Shopify 側に今回設定する URL となる。例: http://ac2a51a0.ngrok.io
5. ブラウザで一旦、このアドレスにアクセスし、localhost の場合と同じ結果か確認する。

※ `4567` というのは Sinatra のデフォルトポートなので、各自起動しているサーバーのポート番号に置き換えること

## 動作確認

`ngrok` が準備できたら、後は RequestBin のときと同じで、Shopify の管理画面の Webhook の設定の URL に ngrok で発行されている URL (https の方)を設定する。

後は、テスト送信で内容を動作を検証したり、実際にストアを動かして、正常にリクエストを処理できるか確認する。


## 備考

Shopify との連携という意味でいうと、Shopify アプリを作るという手段もある。それについては、[Shopify 用のプライベートアプリを作ってみた](http://qiita.com/kumatronik/items/d4bdefabf72af8cc5cdc)で説明している。