Next.jsで作成したREST APIで、StripeのWebhookイベントを安全に処理する方法


Stripeをアプリケーションで最大限活用するには、Webhookが欠かせません。

そこで今回は、Next.jsでWebhook APIを用意する場合に注意したいポイントについて紹介します。

:::note
Next.js(v13)から追加された、「App Router」での対応方法は、次の記事をご覧ください。

https://qiita.com/hideokamoto/items/4c8470dc899208d26533

:::

# 動画版（英語）

英語ですが、Webhookだけでなく、Next.jsでStripeを利用する方法について紹介されています。

<iframe width="560" height="315" src="https://www.youtube.com/embed/nVvDr6MyEXE?start=600" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

# Stripe Webhookを実装する際に注意したいこと

Webhook APIを開発する際の注意点として、「外部からのAPIリクエストを拒絶する必要がある」点があります。

意図した送信元以外からのAPIリクエストでも、処理が実行されてしまった場合に意図しない動作が起こる恐れがあります。

Stripeの場合、Webhook用の署名シークレットを発行し、それを利用してリクエストを検証することができます。

## Webhook　署名シークレットの発行方法

Webhookの署名シークレットは、エンドポイント登録時に発行されます。
Dashboardにログインし、[開発者 > Webhook]から「エンドポイントの追加」をクリックすることで、Webhookを新しく追加できます。

![スクリーンショット 0003-12-24 15.57.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/d1670081-268c-fe47-bc87-913307e9fb20.png)

登録後に表示される詳細ページの、　[署名シークレット]からキーを取得しましょう。

![スクリーンショット 0003-12-24 16.00.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/f0675bb8-c562-356c-8af6-ed4f6854b216.png)

### Stripe CLIを使う場合

Stripe CLIを利用する場合、以下のコマンドを実行します。

```bash
% stripe listen --forward-to localhost:3000/api/webhook
⡿ Checking for new versions...
⣟ Getting ready... > Ready! You are using Stripe API Version [2020-08-27]. Your webhook signing secret is whsec_xxxxx (^C to quit)
```

表示されている`whsec_xxx`が署名シークレットです。

# 署名シークレットの検証方法

発行したシークレットの検証には、APIで受け取ったリクエストBodyのデータと、ヘッダー情報を利用します。
Next.jsやExpressの場合、該当部分だけをまとめると以下のような実装です。

```js
const endpointSecret = 'whsec_xxx'
const sig = request.headers['stripe-signature'];
const event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret);
```

`endpointSecret`に先ほど取得した署名シークレットを入力します。環境変数やAWSのSystems Managerなどを利用すると、より安全かつ交換可能な形にできます。

## 検証に失敗した場合（Stripe以外からのリクエストの場合）

`stripe.webhooks.constructEvent`での検証に失敗すると、エラーがthrowされます。
ですので、try catchを利用して検証エラーを返すようにできます。

以下の例では、TypeScriptで実装する場合に発生する、`undefined`や`null`の処理部分を追加しています。

```js
  const endpointSecret = 'whsec_xxx'
  const sig = request.headers['stripe-signature'];
  let event
  try {
    if (!sig) throw new Error("No signature provided")
    event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret);
  } catch (e) {
    const err = e instanceof Error ? e : new Error("Bad Request")
    response.status(400).send(`Webhook Error: ${err.message}`);
    return;
  }
```

## Next.jsでWebhookの署名シークレットを検証する際の注意点

Next.jsのAPIで実装する場合の注意点が、「Next.js側でリクエストBodyがある程度パースされていること」です。

`stripe.webhooks.constructEvent`が想定しているデータと異なる内容が送られてくるため、そのまま`request`を渡すと以下のエラーが発生します。

```bash
StripeSignatureVerificationError: No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe? https://github.com/stripe/stripe-node#webhook-signing
```

## Next.jsで、APIリクエストのRawデータを受け取れるようにする

Next.jsのAPIは、`pages/api/`配下の該当APIを記述したファイルに、以下のコードを追加することで、リクエストBodyのパースを停止できます。

```js
export const config = {
    api: {
        bodyParser: false
    }
}
```

その後、`micro`などのライブラリを利用してデータを変換してやることで、Stripe SDKの提供する署名チェック処理を実行できます。

```js
import Stripe from 'stripe'
import {buffer} from 'micro'

const endpointSecret = 'whsec_xxxxxx'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
  apiVersion: '2020-08-27'
})

export const config = {
    api: {
        bodyParser: false
    }
}
export default async function handler(
  request,
  response
) {
  const sig = request.headers['stripe-signature'];
  const buf = await buffer(request)

  let event;
  try {
    if (!sig) throw new Error("No signature provided")
    event = stripe.webhooks.constructEvent(buf, sig, endpointSecret);
  } catch (e) {
    const err = e instanceof Error ? e : new Error("Bad Request")
    console.log(err)
    response.status(400).send(`Webhook Error: ${err.message}`);
    return;
  }

  console.log(event)

  return response.status(200).end()
}
```



# [PR] Stripe開発者向け情報をQiitaにて配信中！
2021年12月よりQiitaにて、Stripe開発者のためのブログ記事更新を開始しました。

- [Stripe Updates]：開発者向けStripeアップデート紹介・解説
- ユースケース別のStripe製品や実装サンプルの紹介
- Stripeと外部サービス・OSSとの連携方法やTipsの紹介
- 初心者向けのチュートリアル(予定)

など、Stripeを利用してオンラインビジネスを始める方法について随時更新してまいります。

-> [Stripe Organizationsをフォローして最新情報をQiitaで受け取る](https://qiita.com/organizations/stripe)