RubyでShopify Webhookの使い方を解説

Shopify アプリを開発しているフルスタックプログラマーの森本です。

今回は、ShopifyのWebhookの受信、送信方法を解説して行きます。

こちらのチュートリアルを参考にしています。

About webhooks
Use event data delivered through webhooks to stay in sync with Shopify or execute code after a specific event occurs in a shop.

アプリ開発において、Webhookはさまざまな場面で利用します。今回はRubyを使ったWebhookの使い方をご紹介して行きます。

対象読者
  • Webhookを使いたい方
  • Shopifyアプリ開発者
  • Rubyがある程度理解できる

参考になったと感じたら、コメントをいただけると嬉しいです^^

執筆の励みになります!

Webhookとは

Shopifyでアプリ開発をしていると、購入情報や顧客情報の更新をキャッチしたい場面が出てきます。

APIで定期的に要求して更新情報がないか取得できますが、アプリの負荷やリアルタイム性を犠牲にします。

アプリからShopifyへの問い合わせではなく、特定のアクションをトリガーにShopifyからアプリに通知する仕組みがWebhookです。

実際、Webhookは通知することを主目的として使われることがほとんどです。

ShopifyのWebhookのルールとして、” 決められたアクション ” に対して、” 10秒以内 “の変更をまとめて通知する仕組みになっています。

これは、ユーザーが連続した変更を行うたびに通知するとシステムに負荷がかかる懸念を解決します。

ユーザーは変更途中のデータは必要なく、最終的に変更が完了したデータのみが必要なためとても良いアイディアです。

Webhookテストアプリ構築

チュートリアルからRubyのコードを参考にしました。

app.rbというファイルを作成して、次のコードをコピペしてください。

$ touch app.rb
require 'rubygems'
require 'base64'
require 'openssl'
require 'sinatra'
require 'active_support/security_utils'
require 'shopify_api'

# 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)
    calculated_hmac = Base64.strict_encode64(OpenSSL::HMAC.digest('sha256', SHARED_SECRET, data))
    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

次はGemfileを作成します。Rubyのコードなのでgem経由で必要なライブラリを準備します。

また、このチュートリアルではRubyのフレームワークとしてsinatraを採用しています。

Railsほど大規模なアプリではなくちょっとテストしたい時に便利です。

$ touch Gemfile
source "https://rubygems.org"
gem "shopify_api"
gem "sinatra"
gem "activesupport"
gem "dotenv"
$ bundle install --path=vendor/bundle

これで準備完了です。

app.rbの中に、「SHARED_SECRET = ‘my_shared_secret’」というコードがあります。

これは、Shopify webhookからきた通知による変更なのか、アプリ内の処理による変更なのかを判断するために使われます。

エンドポイントの設定

app.rbではルートディレクトリで受信するようになっているので、エンドポイントを変更します。

...省略
post '/webhook/product_update' do
  request.body.rewind
...省略

また、Shopifyではwebhookの通知にはhttpsである必要があるため、localhostを利用することができません。

その対策として、タネリングサービスを利用します。今回は、ngrokというサービスを使います。(使い方の説明は省略します)

ポートは「4567」を指定してください。Rubyを起動させたときのデフォルトのポート番号となっています。

$ ngrok http 4567

ここで生成されたエンドポイントをShopifyに登録します。

Webhookの受信設定

何をトリガーとしてwebhookを通知するのか、Shopifyの管理画面上から設定することができます。

管理画面の「設定」>「通知」画面に移動し、一番下にある「Webhookを作成」ボタンを押してください。

ShopifyのWebhookでは、「イベント」、「フォーマット(JSON, XML)」、「URL(エンドポイント)」、「APIのバージョン」を選択できます。

Shopifyでは、3ヶ月おきくらいにバージョンを更新しており、最大12カ月間サポート期間があります。

さて、商品を更新した時にwebhookを受信する設定にしたいと思います。

  • イベント:商品更新
  • フォーマット:JSON
  • URL:https://fae6f397dacd.ngrok.io/webhook/product_update
  • APIバージョン:2021-01(最新)

※URLは、ご自身で発行したngrokのドメインに設定してください。

設定すると署名の共有キー(塗りつぶした黒いところ)が発行されます。このキーをapp.rbのSHARED_SECRETに設定します。

念の為、環境変数に登録しておきます。

$ touch .evn
SHARED_SECRET=xxxxxxx共有キーxxxxxxxxxx

app.rbを変更しましょう。

...省略
require 'dotenv' # 追加
Dotenv.load      # 追加
SHARED_SECRET = ENV['SHARED_SECRET']
...省略

これでアプリケーションはwebhookを受信できるようになりました。

実際に起動させて確認してみます。

$ bundle exec ruby app.rb

次に、先程のWebhookを設定したShopify画面に戻り、「テスト通知を送信する」をクリックしてください。

そうすると次のような「Webhook verified: true」が表示されます。

コードの解説

app.rbの中身を説明して行きます。

request.body.rewind
data = request.body.read

request.bodyは、Shopifyがリクエストで送信する全てのデータが格納されています。

rewindでデータを整理した後に、readで全てのデータを取得し、data変数に格納しています。

verified = verify_webhook(data, env["HTTP_X_SHOPIFY_HMAC_SHA256"])
def verify_webhook(data, hmac_header)
    calculated_hmac = Base64.strict_encode64(OpenSSL::HMAC.digest('sha256', SHARED_SECRET, data))
    ActiveSupport::SecurityUtils.secure_compare(calculated_hmac, hmac_header)
end

データをsha256暗号化base64エンコーディングして実行しています。

Base64.strict_encode64 がhmacに変換するための全ての処理を行ってくれています。

ActiveSupport::SecurityUtils.secure_compareが、webhookのヘッダーと比較・検証を行っています。

これにより、安全で外部からの攻撃を防いでくれます。検証が成功すると、trueを返す仕様となっています。

内容を理解できたところで、カスタマイズしましょう。

テストアプリのカスタマイズ

カスタマイズ内容は、何か商品が更新されると「確認中」というタグを自動で付与する機能を作ります。

これは、スタッフの誰かが更新作業を行った時、責任者に確認をしてもらう作業を想定しています。

ここでは完全なアプリにすることなく、更新されたら「確認中」タグを付与することを目的とします。

webhookでは、shopifyからアプリへの通信でしたが、今回はアプリからShopifyへのアクセスが必要です。

実はそのための準備として、shopify_apiのgemを導入済みです。

GitHub - Shopify/shopify-api-ruby: ShopifyAPI is a lightweight gem for accessing the Shopify admin REST and GraphQL web services.
ShopifyAPI is a lightweight gem for accessing the Shopify admin REST and GraphQL web services. - Shopify/shopify-api-ruby

これを使ってAPIの開発を行います。

まずは、Shopifyの管理画面からプライベートアプリを構築しましょう

Shopify管理画面 > アプリ管理 > プライベートアプリを管理する > 新しいプライベートアプリを作成する

アプリ名と緊急連絡先を入力してください。

「 非アクティブなAdmin API権限を表示する」をクリックすると、許可できる一覧が出てきます。

ここで「商品管理」を「読み取りおよび書き込み」に設定し、保存します。

作成したプライベートアプリの「URLの例」をコピーしましょう。

コピーした内容を環境変数に登録します。ただし、「/admin/api」以降は必要ないので削除しておきましょう。

.envに追加します。

SHOP_URL=コピーしたURL(/admin/api以降は削除)

次に、app.rbにShopifyAPIのセッティングを行います。

...省略
API_KEY = ENV["API_KEY"]
ShopifyAPI::Base.api_version = "2021-01" # 追加
ShopifyAPI::Base.site = ENV["SHOP_URL"]  #追加

helpers do
...省略

これで、app.rbとShopify とのAPIの設定が完了します。

商品が更新されたら、「確認中」タグをつけるように編集します。

...省略
  puts "Webhook verified: #{verified}"
  # 以下を追加
  json_data = JSON.parse data
  puts json_data["id"].to_i
  product = ShopifyAPI::Product.find(json_data["id"].to_i)
  puts product
  product.tags += ", 確認中"
  product.save
  return [200, "webhook 通信 & 商品タグ更新完了"]
end

最後にreturne で Shopify に status 200を返しているのはwebhookが常に受信可能か不明なためです。

応答がないと、48時間で19回リトライを行い、その後webhook送信リクエストは削除されます。

また、現状ではhmacの認証がfalseだったとしてもAPIが動いています。

この状況は問題なので、検証が失敗した場合は、status 403を返すように変更しましょう。

...省略
data = request.body.read
verified = verify_webhook(data, env["HTTP_X_SHOPIFY_HMAC_SHA256"])
unless verified
  return [403, "アクセス権限がありません"]
end

puts "Webhook verified: #{verified}"
...省略

これでアプリのカスタマイズが完成です!

最後に、Shopify管理画面から商品を更新し、10秒後にリロードして「確認中」タグがつくことを確認しましょう!

コメント