Shopify WebhookをAPI GatewayからLambdaで処理する

#概要
備忘録、ハマったポイントを重点的に書く。
ShopifyのWebHookを利用して、注文があった際に注文内容を受信する。
この記事では、Shopifyへ即レスポンス返す方法や、webhookの認証についてまとめる。

# 前提
Shopifyの**プライベートアプリ**として運用します。
公式にアプリとして使用する場合は、Shopify-> APIgateway->Lambdaではなく、Shopify-> EventBridge -> Lambdaという形を使用した方が良い為、下記の公式ブログ記事を見て下さい。

[ShopifyアプリMechanicの開発者はEventBridgeでどのようにスケーリングしたのか]("https://www.shopify.jp/blog/partner-event-bridge-scaling")

# 1. Lambdaでの処理
webHookを受信した時の処理されるLambda関数を作成します。
### Shopify WebHookを3秒以内に返す
Shopify WebHookを**3秒以内にレスポンスを返す必要があります。**レスポンスが遅い場合や失敗した場合はリトライが合計3回行われ、何度も失敗するとwebhookが削除されます。

Lambdaは、呼び出しタイプをEVENTにし、非同期で実行します。これよりLambdaが実行が開始されるとスグに成功したというレスポンスが返ります。
呼び出しをEVENTにすると、処理でエラーが発生した際に2回リトライされる仕様になっています。
私の場合、これを防ぐために、try-catch節でエラーが出ないようにしています。

### WebHookの完全性の認証
WebHookが不正なものでないか認証する機能がShopifyにはあります。
サンプルコードのverify_webhookをいうのがそれに当たります。
SHARED_SECRETはWebHookを設定する画面で確認することができます。（後述 -> Shopifyでの設定）

### サンプルコード
```TypeScript
import crypto from 'crypto'
const SHARED_SECRET = "" // ご自身の共有鍵をここに入力
export const handler=async(event:any, context:Context, callback:Callback)=>{
  try{
   await verify_webhook(event,event.headers['X-Shopify-Hmac-Sha256'])
   // 何からしらの処理
   return
 }catch(e){
    // SlackにPOSTするなど、エラーの確認処理
    return
  }
}

const verify_webhook = async(event:any, hmac_header:any)=>{
  const buffer = (Buffer.from(event.rawbody, 'utf8'))

  const generated = crypto
    .createHmac('sha256',SHARED_SECRET)
    .update(buffer)
    .digest('base64')
  if(generated === hmac_header) return true
  throw new Error('不正なWebHookを受信しました。');
}
```
デプロイしたら完了です。

# 2. API Gatewayの設定
1. APIを作成し、ソースを作成します。本記事ではリソース名を「order-shopify-2」としています。
1. WebHookを受信するためにPOSTメソッドを作成します。
1. 先程作成したLambdaメソッドを指定します。

![dbb1293b-b9c7-4351-93f3-2d2cb849a840.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46348/e19e4407-6c13-7b80-4240-289118afd3e6.jpeg)

### APIGatewayからLambdaを非同期で呼び出す。
統合リクエスト -> HTTPヘッダーを下記に設定します。
名前：X-Amz-Invocation-Type
マッピング元：'Event'
![スクリーンショット 2021-11-14 16.13.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46348/69b6780a-a24b-9a5b-9314-5ffe2a4682ff.png)

### マッピングテンプレート
**WebHookの完全性を認証するのに必要になります。**逆に完全性を認証処理を加えないのであれば、設定しなくても大丈夫です。
統合リクエスト -> HTTPヘッダーを下記に設定します。
リクエスト本文のパススルー：テンプレートが定義されていない場合（推奨）
Content-Type: application/json
テンプレート

```
{
  "rawbody": "$util.escapeJavaScript($input.body)",
  "headers": {
    #foreach($header in $input.params().header.keySet())
      "$header": "$util.escapeJavaScript($input.params().header.get($header))" #if($foreach.hasNext),#end
    #end
  },
  "method": "$context.httpMethod",
  "params": {
    #foreach($param in $input.params().path.keySet())
      "$param": "$util.escapeJavaScript($input.params().path.get($param))" #if($foreach.hasNext),#end

    #end
  },
  "query": {
    #foreach($queryParam in $input.params().querystring.keySet())
    "$queryParam": "$util.escapeJavaScript($input.params().querystring.get($queryParam))" #if($foreach.hasNext),#end
    #end
  }
}
```

![863e3e0f-763d-4128-a72e-2fb1a0bb957b.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46348/b3aa26ee-3446-0a46-5f0f-acd7327a4b3a.jpeg)

保存をクリックし、デプロイして下さい。

### POST先のURL
こんなフォーマットになると思います
`https://{あなたのAPI}.execute-api.ap-northeast-1.amazonaws.com/Stage/order-shopify-2`

# 3. Shopifyでの設定
###WeHookの設定
Shopify管理画面から「設定」、「通知」と行き、1番下までスクロール
「WebHookを作成」をクリック。
![スクリーンショット 2021-11-14 16.22.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46348/30118857-e9e1-dc3b-dbe2-eb7ae698a259.png)

注文があった際に、WebHookを受け取りたいのであれば、イベントは「注文作成」にします。
その他は画像の通り。
URLは先程、APIGatewayで作成したURLを入力。

###共有鍵
「すべてのWebhookは****************************************************でサインされるので、完全性を検証することができます。」書かれていると思うのでキーをコピーしてLambdaに入力して下さい。
忘れずに再度、Lambdaでデプロイします。

###WebHookのテスト
![bfeacbf8-19bf-4083-8811-970b30b619a7.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46348/1820f26c-cedc-63e6-c63c-9c910bdf008c.jpeg)

作成したWebHookを右へスクロールすると、「テスト通知を送信する」というボタンがあります。これをクリックするとWebHookをテスト出来ます。