StripeのWebhookで署名チェックを行う(Node.js)

この記事はJP_Stripesアドベントカレンダーの記事です。

StripeのWebhookとして作成したAPIを、StripeのWebhook以外から実行できない様にする実装をNode.jsで行います。

なぜ署名チェックするのか

公開されたAPIになるので、外部から自由にコールされるのはいろいろよろしくありません。

StripeのWebhookがなげてくるフォーマットを知っている人であれば、変な値を投げ込んでくるということもできなくはないです。

署名をどこで入手するのか

Webhookの詳細画面に[署名シークレット]という項目があります。ここから署名を取得できます。

署名の管理にはAWS Secret Managerを使おう

署名を取得してもそれをコードにベタ打ちなどすると、ローテーションができない・一度でもコードをDLした人がいればそこから漏れる可能性があるなどの問題があります。

AWSを使う場合は、Secret Managerを使うことで安全に管理できます。

Node.jsでSecret Managerの値を取得しつつ署名チェックを行う

あとは署名チェックを実装するだけです。以下のサンプルコードでは、ついでにStripeのAPIキーもSecret Managerにいれていますが、Systems Managerのパラメータストアに暗号化して保存 -> 環境変数として利用でもよいかもしれません。

import 'tslib'
import { APIGatewayProxyHandler } from 'aws-lambda'
import Stripe from 'stripe'
import {
  SecretsManager
} from 'aws-sdk'

const client = new SecretsManager()

export const handler:APIGatewayProxyHandler = async (event) => {
  try {
    // StripeのSignature取得
    const signature = event.headers["Stripe-Signature"]

    // Secret Managerから署名を取得
    const { SecretString: secret } = await client.getSecretValue({
      // Secret Managerで登録した名前
      SecretId: 'YOUR_SECRET_MANAGER_SECRET_NAME'
    }).promise()
    if (!secret) throw new Error('No such secret')

    // StripeのAPIキーもSecret Managerで管理するとより安全
    const { SecretString: apiKey } = await client.getSecretValue({
      // Secret Managerで登録した名前
      SecretId: 'YOUR_STRIPE_API_KEY'
    }).promise()
    if (!apiKey) throw new Error('No API KEY')
    const stripe = new Stripe(apiKey)

    const eventReceived = stripe.webhooks.constructEvent(event.body, signature, secret)
    return {
      statusCode: 200,
      body: JSON.stringify({
        message: 'Hello Webhook',
        eventReceived
      })
    }
  } catch (e) {
    return {
      statusCode: 400,
      body: JSON.stringify({
        message: 'Invalid signature'
      })
    }
  }
}

参考

Stripe – Checking Webhook Signature

Comment