# Webhook エンドポイントで Stripe イベントを受信する

Webhook エンドポイントで Stripe からのイベントをリッスンすることで、導入で自動的にリアクションをトリガーできます。

HTTPS Webhook エンドポイントを作成してイベントを受信できます。Webhook エンドポイントを登録すると、Stripe アカウントで[イベント](https://docs.stripe.com/event-destinations.md#events-overview)が発生したときに、Stripe からそのエンドポイントにリアルタイムのデータが送信されます。Stripe は HTTPS を使用して、イベント情報が含まれる JSON ペイロードとして、アプリに Webhook イベントを送信します。

Webhook イベントを受信すると、顧客の銀行が決済を確認したとき、顧客が不審請求の申し立てを行ったとき、または継続支払いが成功したときなど、非同期イベントに応答できます。

イベントを [Amazon EventBridge](https://docs.stripe.com/event-destinations/eventbridge.md) または [Azure Event Grid](https://docs.stripe.com/event-destinations/eventgrid.md) に直接送信することにより、AWS または Azure のインフラストラクチャで Stripe イベントを使用することもできます。

以下の手順に従い、アプリで Webhook イベントの受信を始めます。1 つのエンドポイントを登録して作成し、同時に複数の異なるイベントタイプを処理することも、特定のイベント用に個々のエンドポイントを設定することもできます。

## エンドポイントの設定

[API](https://docs.stripe.com/api/v2/event-destinations.md) またはワークベンチの [Webhook](https://dashboard.stripe.com/webhooks) タブを使用して、Stripe がイベント送信先を認識できるように、Webhook エンドポイントのアクセス可能な URL を登録します。Stripe には最大 16 個の Webhook エンドポイントを登録できます。登録する Webhook エンドポイントは、一般公開されているアクセス可能な HTTPS URL である必要があります。

- ローカルホストサーバーがあるものの、一般公開されているアクセス可能な HTTPS URL がない場合は、[ngrok](https://ngrok.com/) などのトンネリングツールを使用することで、テスト用の一般公開されるアクセス可能な HTTPS URL を一時的に生成できます。
- あるいは、一般公開されているアクセス可能な HTTPS URL を登録する前に、[Stripe CLI を使用してローカル環境でテストする](https://docs.stripe.com/webhooks.md#local-listener)ことができます。

### Webhook の URL 形式

Webhook エンドポイントを登録するための URL 形式は、次のとおりです。

```
https://<your-website>/<your-webhook-endpoint>
```

たとえば、ドメインが `https://mycompanysite.com` で、Webhook エンドポイントへのルートが `@app.route('/stripe_webhooks', methods=['POST'])` である場合、エンドポイント URL として `https://mycompanysite.com/stripe_webhooks` を指定します。

### Webhook エンドポイントのイベント送信先を作成する

#### ダッシュボード

ダッシュボードで新しい Webhook エンドポイントを作成するには、以下のようにします。

1. ワークベンチの [Webhook](https://dashboard.stripe.com/webhooks) タブを開きます。
2. **イベント送信先を作成**をクリックします。
3. **アカウント**を選択して、自社のアカウントからのイベントを受信します。
4. 使用する[イベントオブジェクト](https://docs.stripe.com/api/events.md)の API バージョンを選択します。
5. Webhook エンドポイントに送信する[イベントタイプ](https://docs.stripe.com/api/events/types.md)を選択します。
6. **続行**を選択して、送信先のタイプとして **Webhook エンドポイント**を選択します。
7. **続行**をクリックして、Webhook の**エンドポイント URL** とオプションの説明を指定します。
8. Webhook の設定ページに、`whsec_` で始まる署名シークレットが表示されます。**シークレットを表示**をクリックし、その値をコピーして、[ハンドラーを作成する](https://docs.stripe.com/webhooks.md#webhook-endpoint-def)際に使用します。

> [ワークベンチ](https://docs.stripe.com/workbench.md)により、既存の[開発者ダッシュボード](https://docs.stripe.com/development/dashboard.md)が置き換えられます。引き続き開発者ダッシュボードで[新しい Webhook エンドポイントを作成する](https://docs.stripe.com/development/dashboard/webhooks.md)ことも可能ですが、ワークベンチの使用をお勧めします。

#### API

[/v2/core/event_destinations](https://docs.stripe.com/api/v2/event-destinations.md) エンドポイントを使用して、新しいエンドポイントを登録します。

#### スナップショットイベント

自社のアカウントからの[スナップショットイベント](https://docs.stripe.com/api/events/types.md)を受信するには、[event_payload](https://docs.stripe.com/api/v2/core/event-destinations/create.md#v2_create_event_destinations-event_payload) の値を `snapshot` に設定し、[enabled_events](https://docs.stripe.com/api/v2/core/event-destinations/create.md#v2_create_event_destinations-enabled_events) の値を Webhook エンドポイントに送信するイベントタイプに設定します。

```curl
curl -X POST https://api.stripe.com/v2/core/event_destinations \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2026-08-26.preview" \
  --json '{
    "name": "My event destination",
    "type": "webhook_endpoint",
    "events_from": [
        "@self"
    ],
    "event_payload": "snapshot",
    "enabled_events": [
        "payment_intent.succeeded",
        "payment_intent.payment_failed"
    ],
    "webhook_endpoint": {
        "url": "https://mycompanysite.com/webhook"
    },
    "include": [
        "webhook_endpoint.signing_secret"
    ]
  }'
```

出力には `whsec_` で始まる `webhook_endpoint.signing_secret` の値が含まれます。この値をコピーして、[ハンドラーを作成する](https://docs.stripe.com/webhooks.md#webhook-endpoint-def)際に使用します。

#### シンイベント

[シンイベント](https://docs.stripe.com/api/v2/core/events/event-types.md)を使用している場合は、別の Webhook エンドポイントを登録する必要があります。[シンイベントとスナップショットイベントの違い](https://docs.stripe.com/event-destinations.md#events-overview)に関する詳細をご確認ください。

自社のアカウントからの[シンイベント](https://docs.stripe.com/api/v2/core/events/event-types.md)を受信するには、[event_payload](https://docs.stripe.com/api/v2/core/event-destinations/create.md#v2_create_event_destinations-event_payload) の値を `thin` に設定し、[enabled_events](https://docs.stripe.com/api/v2/core/event-destinations/create.md#v2_create_event_destinations-enabled_events) の値を Webhook エンドポイントに送信するイベントタイプに設定します。

```curl
curl -X POST https://api.stripe.com/v2/core/event_destinations \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2026-08-26.preview" \
  --json '{
    "name": "My event destination",
    "type": "webhook_endpoint",
    "events_from": [
        "@self"
    ],
    "event_payload": "thin",
    "enabled_events": [
        "v1.billing.meter.error_report_triggered"
    ],
    "webhook_endpoint": {
        "url": "https://mycompanysite.com/webhook"
    },
    "include": [
        "webhook_endpoint.signing_secret"
    ]
  }'
```

出力には `whsec_` で始まる `webhook_endpoint.signing_secret` の値が含まれます。この値をコピーして、[ハンドラーを作成する](https://docs.stripe.com/webhooks.md#webhook-endpoint-def)際に使用します。

### 登録済み URL を使用しないローカルテスト

登録済みのアクセス可能な HTTPS URL がない場合は、Stripe CLI を使用してローカル環境で Webhook をテストして、[ローカルの Webhook エンドポイントにイベントを転送する](https://docs.stripe.com/cli/use-cli#forward-events-to-your-local-webhook-endpoint)ことができます。

1. Stripe CLI をまだインストールしていない場合は、マシンに[インストール](https://docs.stripe.com/cli/install)します。

2. Stripe アカウントにログインし、コマンドラインで `stripe login` を実行して CLI を設定します。

3. イベントのスコープとタイプに応じて、[stripe listen](https://docs.stripe.com/cli/listen) を実行し、ローカルホストがシミュレートされたイベントを受信できるようにします。

   #### スナップショットイベントの転送

   次のコマンドを使用して、アカウントからの[スナップショットイベント](https://docs.stripe.com/event-destinations.md#events-overview)をローカルリスナーに転送します。

   ```bash
   stripe listen --forward-to localhost:4242/webhook
   ```

   #### シンイベントの転送

   次のコマンドを使用して、アカウントからの[シンイベント](https://docs.stripe.com/event-destinations.md#events-overview)をローカルリスナーに転送します。

   ```bash
   stripe listen --forward-thin-to localhost:4242/webhook --thin-events "*"
   ```

   このコマンドは、ポート 4242 にローカルホストのウェブサイトと `POST /webhook` エンドポイントがあることを前提としています。これは[ハンドラーを作成する](https://docs.stripe.com/webhooks.md#webhook-endpoint-def)際に設定できます。

4. `stripe listen` コマンドは `{{WEBHOOK_SIGNING_SECRET}}` を出力します。この値をコピーして、[ハンドラーを作成する](https://docs.stripe.com/webhooks.md#webhook-endpoint-def)際に使用します。

   ```output
   Ready! Your webhook signing secret is '{{WEBHOOK_SIGNING_SECRET}}' (^C to quit)
   ```

> `--forward-to` 引数を `stripe listen` で使用するには、ターミナルで [Stripe CLI](https://docs.stripe.com/cli.md) を使用してコマンドを実行する必要があります。[ワークベンチのシェル](https://docs.stripe.com/workbench/shell.md)は `--forward-to` 引数をサポートしていないため、このコマンドをシェルで実行することはできません。

## ハンドラを作成する

POST メソッドを使用して、Webhook リクエストの受け付けが可能な HTTP または HTTPS エンドポイント関数を設定します。ローカルマシンでエンドポイント関数を開発中の場合、HTTP を使用できます。公開アクセスが可能になったら、Webhook エンドポイント関数は HTTPS を使用する必要があります。

Stripe API リファレンスを使用して、Webhook ハンドラーで処理する必要のある[シンイベントオブジェクト](https://docs.stripe.com/api/v2/core/events/event-types.md)または[スナップショットイベントオブジェクト](https://docs.stripe.com/api/events/types.md)を特定します。

エンドポイント関数を設定し、以下を行うようにします。

- イベント情報が含まれる JSON ペイロードを使用した POST リクエストを処理します。
- JSON ペイロード、`Stripe-Signature` ヘッダー、前のステップの `whsec_` Webhook 署名シークレットを使用して、Webhook リクエストが Stripe によって生成されたことを確認します。確認に失敗すると、エラーが表示されます。
- タイムアウトを引き起こす可能性のある複雑なロジックが実行される前に、成功のステータスコード (`2xx`) を素早く返します。たとえば、会計システムで顧客の請求書を支払い済みとして更新する前に、`200` のレスポンスを返してください。

> #### 生のリクエストボディは操作しないでください
> 
> Stripe で署名の検証を実行するには、未加工のリクエスト本文が必要です。フレームワークを使用している場合は、元の本文に手が加えられないようにする必要があります。 未加工のリクエスト本文に何らかの変更が行われた場合、検証は失敗します。
> 
> [署名確認エラーのトラブルシューティング](https://docs.stripe.com/webhooks/signature.md)方法をご覧ください。

#### エンドポイントの例

このコードスニペットは、Stripe アカウントから受信したイベントを確認し、イベントを処理して、`200` レスポンスを返すように設定された Webhook 関数です。API v1 リソースを使用する場合は[スナップショット](https://docs.stripe.com/event-destinations.md#events-overview)イベントハンドラを参照し、API v2 リソースを使用する場合は[シン](https://docs.stripe.com/event-destinations.md#events-overview)イベントハンドラを参照します。

#### スナップショットイベントハンドラ

スナップショットイベントハンドラを作成するときは、イベントの `data.object` フィールドにアクセスして、イベント発生時の API オブジェクト定義をロジックに使用します。また、Stripe API から API リソースを取得して、最新のオブジェクト定義にアクセスすることもできます。

#### Ruby

```ruby
require 'json'
require 'stripe'

client = Stripe::StripeClient.new(ENV.fetch('STRIPE_API_KEY'))

# Replace this endpoint secret with your unique endpoint secret key
# If you're testing with the CLI, run 'stripe listen' to find the secret key
# If you defined your endpoint using the API or the Dashboard, check your webhook settings for your endpoint secret: https://dashboard.stripe.com/webhooks
endpoint_secret = 'whsec_...';

# Using Sinatra
post '/webhook' do
  payload = request.body.read
  event = nil

  begin
    event = Stripe::Event.construct_from(
      JSON.parse(payload, symbolize_names: true)
    )
  rescue JSON::ParserError => e
    # Invalid payload
    status 400
    return
  end

  # Check that you have configured webhook signing
  if endpoint_secret
    # Retrieve the event by verifying the signature using the raw body and the endpoint secret
    signature = request.env['HTTP_STRIPE_SIGNATURE'];
    begin
      event = Stripe::Webhook.construct_event(
        payload, signature, endpoint_secret
      )
    rescue Stripe::SignatureVerificationError => e
      puts "⚠️  Webhook signature verification failed. #{e.message}"
      status 400
    end
  end

  # Handle the event
  case event.type
  when 'payment_intent.succeeded'
    payment_intent = event.data.object # contains a Stripe::PaymentIntent
    # Then define and call a method to handle the successful payment intent.
    # handle_payment_intent_succeeded(payment_intent)
  when 'payment_method.attached'
    payment_method = event.data.object # contains a Stripe::PaymentMethod
    # Then define and call a method to handle the successful attachment of a PaymentMethod.
    # handle_payment_method_attached(payment_method)
  # ... handle other event types
  else
    puts "Unhandled event type: #{event.type}"
  end

  status 200
end
```

#### Thin イベントハンドラー (Clover+)

シンイベントハンドラーを作成する際は、`fetchRelatedObject()` メソッドを使用して、イベントに関連付けられた最新バージョンのオブジェクトを取得します。イベントには、`EventNotification` の `.fetchEvent()` インスタンスメソッドからのみ取得できる[追加データ](https://docs.stripe.com/event-destinations.md#fetch-data)が含まれている場合があります。そのデータの正確な形式は、イベントの `type` によって異なります。

SDK バージョンでクラスを生成するには、リリース時にイベントタイプが使用可能である必要があります。SDK にクラスがないイベントを処理するには、`UnknownEventNotification` クラスを使用します。

#### Python

```python
import os
from stripe import StripeClient
from stripe.events import UnknownEventNotification

from flask import Flask, request, jsonify

app = Flask(__name__)
api_key = os.environ.get("STRIPE_API_KEY", "")
webhook_secret = os.environ.get("WEBHOOK_SECRET", "")

client = StripeClient(api_key)

@app.route("/webhook", methods=["POST"])
def webhook():
    webhook_body = request.data
    sig_header = request.headers.get("Stripe-Signature")

    try:
        event_notif = client.parse_event_notification(
            webhook_body, sig_header, webhook_secret
        )

        # type checkers will narrow the type based on the `type` property
        if event_notif.type == "v1.billing.meter.error_report_triggered":
            # in this block, event_notification is typed as
            # a V1BillingMeterErrorReportTriggeredEventNotification

            # there's basic info about the related object in the notification
            print(f"Meter w/ id {event_notif.related_object.id} had a problem")

            # or you can fetch the full object form the API for more details
            meter = event_notif.fetch_related_object()
            print(
                f"Meter {meter.display_name} ({meter.id}) had a problem"
            )

            # And you can always fetch the full event:
            event = event_notif.fetch_event()
            print(f"More info: {event.data.developer_message_summary}")

        elif event_notif.type == "v1.billing.meter.no_meter_found":
            # in this block, event_notification is typed as
            # a V1BillingMeterNoMeterFoundEventNotification

            # that class doesn't define `fetch_related_object` because the event
            # has no related object.
            # so this line would correctly give a type error:
            # meter = event_notif.fetch_related_object()

            # but fetching the event always works:
            event = event_notif.fetch_event()
            print(
                f"Err! No meter found: {event.data.developer_message_summary}"
            )

        # Events that were introduced after this SDK version release are
        # represented as `UnknownEventNotification`s.
        # They're valid, the SDK just doesn't have corresponding classes for them.
        # You must match on the "type" property instead.
        elif isinstance(event_notif, UnknownEventNotification):
            # these lines are optional, but will give you more accurate typing in this block
            from typing import cast

            event_notif = cast(UnknownEventNotification, event_notif)

            # continue matching on the type property
            # from this point on, the `related_object` property _may_ be None
            # (depending on the event type)
            if event_notif.type == "some.new.event":
                # if this event type has a related object, you can fetch it
                obj = event_notif.fetch_related_object()
                # otherwise, `obj` will just be `None`
                print(f"Related object: {obj}")

                # you can still fetch the full event, but it will be untyped
                event = event_notif.fetch_event()
                print(f"New event: {event.data}")  # type: ignore

        return jsonify(success=True), 200
    except Exception as e:
        return jsonify(error=str(e)), 400
```

## ハンドラをテストする

Webhook エンドポイント機能を本番環境で有効化する前に、サンドボックスでイベントをトリガーするか、[Stripe CLI](https://docs.stripe.com/cli.md) を使用してテストイベントを送信し、アプリケーションの連携をテストすることをお勧めします。

### テストイベントをトリガーする

テストイベントを送信するには、Stripe ダッシュボードでオブジェクトを手動で作成し、イベントの送信先が登録されているイベントタイプをトリガーします。[Stripe for VS Code](https://docs.stripe.com/stripe-vscode.md) を使用してイベントをトリガーする方法をご確認ください。

#### スナップショットイベントをトリガーする

[Stripe Shell](https://docs.stripe.com/workbench/shell.md) または [Stripe CLI](https://docs.stripe.com/cli.md) で次のコマンドを使用できます。この例では、`payment_intent.succeeded` イベントがトリガーされます。

```bash
stripe trigger payment_intent.succeeded
Running fixture for: payment_intent
Trigger succeeded! Check dashboard for event details.
```

#### シンイベントのトリガー

[Stripe CLI](https://docs.stripe.com/cli.md) で次のコマンドを使用できます。この例では、`v1.billing.meter.error_report_triggered` イベントがトリガーされます。

```bash
stripe trigger v1.billing.meter.error_report_triggered
Setting up fixture for: list_billing_meters
Running fixture for: list_billing_meters
Setting up fixture for: billing_meter
Running fixture for: billing_meter
Setting up fixture for: list_billing_meters_after_creation
Running fixture for: list_billing_meters_after_creation
Setting up fixture for: billing_meter_event_session
Running fixture for: billing_meter_event_session
Setting up fixture for: create_billing_meter_event_stream
Running fixture for: create_billing_meter_event_stream
Trigger succeeded! Check dashboard for event details.
```

## Optional: Connect 用のイベント送信先の作成

Connect プラットフォームとして[イベント送信先を作成する](https://docs.stripe.com/webhooks.md#create-webhook-endpoint)際、受信するスコープを選択します。

#### ダッシュボード

- **アカウント**: 自社アカウントのリソースからのイベント。
- **連結アカウント**: 連結アカウントに属するリソースからのイベント。

#### API

- [events_from=[“@self”]](https://docs.stripe.com/api/v2/core/event-destinations/create.md#v2_create_event_destinations-events_from): 自社アカウントのリソースからのイベント。
- [events_from=[“@accounts”]](https://docs.stripe.com/api/v2/core/event-destinations/create.md#v2_create_event_destinations-events_from): 連結アカウントに属するリソースからのイベント。

**連結アカウント**用にイベント送信先を登録すると、連結アカウントに属するリソースからのイベントを受信できます。たとえば、[ダイレクト支払い](https://docs.stripe.com/connect/direct-charges.md)、連結アカウントの顧客および決済手段、入金の失敗のほか、アカウント登録、本人確認、外部アカウントの変更、アカウントのリンク解除といった以前のスナップショットによる連結アカウントのライフサイクルの更新などがあります。Accounts v2 の場合、このスコープでは連結アカウントを表す Account オブジェクトのスナップショットイベントのみを受信します。

Connect の実装によっては、**アカウント**用にイベント送信先の登録が必要になる場合もあります。その理由は以下のとおりです。

- プラットフォームの顧客、プラットフォームが所有する支払い、[デスティネーション支払い](https://docs.stripe.com/connect/destination-charges.md)、[支払いと送金の分離](https://docs.stripe.com/connect/separate-charges-and-transfers.md)など、プラットフォームアカウントのリソースのイベントの処理。
- 連結アカウントを表す Accounts v2 オブジェクトに関連するシンイベントの処理。

詳細については、[Connect Webhook](https://docs.stripe.com/connect/webhooks.md?accounts-namespace=v2) をご覧ください。

### 登録済み URL を使用しないローカル環境での Connect テスト

#### スナップショットイベント

次のコマンドを使用して、連結アカウントからの[スナップショットイベント](https://docs.stripe.com/event-destinations.md#events-overview)をローカルリスナーに転送します。

```bash
stripe listen --forward-connect-to localhost:4242/webhook
```

#### シンイベント

次のコマンドを使用して、連結アカウントからの[シンイベント](https://docs.stripe.com/event-destinations.md#events-overview)をローカルリスナーに転送します。

```bash
stripe listen --forward-thin-connect-to localhost:4242/webhook --thin-events "*"
```

## Optional: 組織用のイベント送信先の作成

組織として[イベント送信先を作成する](https://docs.stripe.com/webhooks.md#create-webhook-endpoint)際、受信するスコープを選択します。

#### ダッシュボード

- **組織内のアカウント**: 組織のアカウントのリソースからのイベント。
- **連結アカウント**: 組織の連結アカウントのリソースからのイベント。

#### API

- [events_from=[“@organization_members”]](https://docs.stripe.com/api/v2/core/event-destinations/create.md#v2_create_event_destinations-events_from): 組織のアカウントのリソースからのイベント。
- [events_from=[“@organization_members/@accounts”]](https://docs.stripe.com/api/v2/core/event-destinations/create.md#v2_create_event_destinations-events_from): 組織の連結アカウントのリソースからのイベント。

### 組織のイベント送信先でサポートされていないイベントタイプの動作

Stripe はほとんどのイベントタイプを非同期で送信しますが、一部のイベントタイプについてはレスポンスを待機します。この場合、イベント送信先が応答するかどうかによって Stripe の動作が異なります。

イベント送信先が[Organization](https://docs.stripe.com/get-started/account/orgs.md)イベントを受信する場合、応答が必要なイベントには以下の制限があります。

- 組織のイベント送信先に `issuing_authorization.request` を登録することはできません。代わりに、組織内の Stripe アカウントに [Webhook エンドポイント](https://docs.stripe.com/webhooks.md#example-endpoint)を設定して、このイベントタイプを登録します。`issuing_authorization.request` を使用して、購入リクエストをリアルタイムで承認します。
- `checkout_sessions.completed`を受信している組織のデスティネーションは、[Checkout](https://docs.stripe.com/payments/checkout.md)をウェブサイトに直接組み込む場合や、顧客を Stripe がホストする決済ページにリダイレクトする場合の[リダイレクト動作](https://docs.stripe.com/checkout/fulfillment.md#redirect-hosted-checkout)を処理できません。Checkout のリダイレクト動作に影響を与えるには、組織内の Stripe アカウントに設定された[Webhook エンドポイント](https://docs.stripe.com/webhooks.md#example-endpoint)でこのイベントタイプを処理します。
- `invoice.created`イベントに対して失敗した応答をする組織のデスティネーションは、[自動回収を使用している場合の自動請求書確定](https://docs.stripe.com/billing/subscriptions/webhooks.md#understand)に影響を与えることができません。自動請求書確定をトリガーするには、組織内の Stripe アカウントに設定された[Webhook エンドポイント](https://docs.stripe.com/webhooks.md#example-endpoint)でこのイベントタイプを処理する必要があります。

#### `context` の使用

#### スナップショットイベント

このコードスニペットは、受信したイベントを確認し、該当する場合は元の口座を検出して、イベントを処理し、`200` のレスポンスを返すよう設定された Webhook 関数です。

#### Ruby

```ruby
require 'json'

client = Stripe::StripeClient.new('sk_...')

# Using Sinatra
post '/webhook' do
  payload = request.body.read
  event = nil

  begin
    event = Stripe::Event.construct_from(
      JSON.parse(payload, symbolize_names: true)
    )
  rescue JSON::ParserError => e
    # Invalid payload
    status 400
    return
  end

  # Extract the context
  context = event.context

  # Define your API key variables (ideally loaded securely)
  ACCOUNT_123_API_KEY = "sk_test_123"
  ACCOUNT_456_API_KEY = "sk_test_456"

  account_api_keys = {
    "account_123" => ACCOUNT_123_API_KEY,
    "account_456" => ACCOUNT_456_API_KEY
  }

  api_key = account_api_keys[context]

  if api_key.nil?
    puts "No API key found for context: #{context}"
    status 400
    return
  end

  # Handle the event
  case event.type
  when 'customer.created'
    customer = event.data.object

    begin

      latest_customer = client.v1.customers.retrieve(customer.id, {api_key: api_key})
      handle_customer_created(latest_customer, context)
    rescue => e
      puts "Error retrieving customer: #{e.message}"
      status 500
      return
    end

  when 'payment_method.attached'
    payment_method = event.data.object

    begin
      latest_payment_method = client.v1.payment_methods.retrieve(payment_method.id, {api_key: api_key})
      handle_payment_method_attached(latest_payment_method, context)
    rescue => e
      puts "Error retrieving payment method: #{e.message}"
      status 500
      return
    end

  else
    puts "Unhandled event type: #{event.type}"
  end

  status 200
end
```

#### Thin イベントハンドラー (Clover+)

`EventNotification` の `context` プロパティを使用して、[組織](https://docs.stripe.com/get-started/account/orgs.md) 内のイベントのアカウントを識別します。`.fetchRelatedObject()` と `.fetchEvent()` を除くすべての API コールに対して [Stripe-Context ヘッダー](https://docs.stripe.com/context.md) を手動で設定する必要があります。これは自動的に行われます。

#### Python

```python
org_api_key = os.environ.get("STRIPE_API_KEY")
webhook_secret = os.environ.get("WEBHOOK_SECRET")
client = StripeClient(org_api_key)

# inside your webhook handler
event_notification = client.parse_event_notification(payload, sig_header, webhook_secret)

# uses `context` automatically
event_notification.fetch_event()

# pass context manually for other API requests
client.v1.invoices.list(stripe_context=event_notification.context)
```

## Webhook 連携のデバッグ

Webhook エンドポイントにイベントを送信する際に、以下のような複数のタイプの問題が発生することがあります。

- Stripe が Webhook エンドポイントにイベントを送信できない可能性がある
- Webhook エンドポイントで SSL の問題が発生している可能性がある
- ネットワーク接続が断続的である
- Webhook エンドポイントが、受信する予定のイベントを受信していない

### イベント配信を表示する

イベント配信を表示するには、[ワークベンチ](https://docs.stripe.com/workbench.md)を開き、**Webhook** で Webhook エンドポイントを選択してから、**イベント配信** タブを選択します。**イベント配信** タブには、イベントのリストと、イベントが`配信済み`、`保留中`、`失敗`のいずれであるかが表示されます。イベントをクリックすると、配信試行の HTTP ステータスコードや、保留中の今後の配信時刻などのメタデータを確認できます。

[Stripe CLI](https://docs.stripe.com/cli.md) を使用して、ターミナルで直接[イベントをリッスンする](https://docs.stripe.com/webhooks.md#test-webhook)こともできます。

### HTTP ステータスコードを修正する

イベントにステータスコード `200` が表示されている場合は、Webhook エンドポイントへの送信が成功したことを示しています。`200` 以外のステータスコードを受信する場合もあります。次の表で、一般的な HTTP ステータスコードと推奨される解決方法の一覧をご覧ください。

| 保留中の Webhook ステータス | 説明 | 修正 |
| --- | --- | --- |
| (接続不可) ERR | 宛先サーバーへの接続を確立できません。 | ホストドメインがインターネットで一般に公開されアクセス可能であることを確認します。 |
| (`302`) ERR (またはその他の `3xx` ステータス) | 宛先サーバーがリクエストを別の店舗にリダイレクトしようとしました。Webhook リクエストへのリダイレクト応答は失敗と見なされます。 | Webhook エンドポイントの送信先を、リダイレクトによって解決される URL に設定します。 |
| (`400`) ERR (またはその他の `4xx` ステータス) | 宛先サーバーがリクエストを処理できないか、処理を拒否しています。これは、サーバーがエラーを検出した場合 (`400`)、宛先 URL にアクセス制限が設定されている場合 (`401`、`403`、`405`)、または宛先 URL が存在しない場合 (`404`) に発生することがあります。 | エンドポイントがインターネットに公開でアクセスでき、POST HTTP メソッドを受け付けていることを確認してください。 |
| (`500`) ERR (またはその他の `5xx` ステータス) | リクエストの処理中に、宛先サーバーでエラーが発生しました。 | アプリケーションのログを確認して、`500` エラーが返されている理由を調べます。 |
| (TLS エラー) ERR | 送信先サーバーへの安全な接続を確立できませんでした。通常、送信先サーバーの証明書チェーン内の SSL/TLS 証明書または中間証明書に問題があると、これらのエラーが発生します。Stripe には *TLS* (TLS refers to the process of securely transmitting data between the client—the app or browser that your customer is using—and your server. This was originally performed using the SSL (Secure Sockets Layer) protocol) バージョン `v1.2` 以降が必要です。 | [SSL サーバーテスト](https://www.ssllabs.com/ssltest/)を実行して、このエラーの原因となった可能性がある問題を見つけます。 |
| (タイムアウト) ERR | 宛先サーバーで Webhook リクエストに応答するのに時間がかかりすぎました。 | Webhook 処理コードで複雑なロジックを延期して、成功を示すレスポンスを即時に返すようにしてください。 |

## イベント送信の動作

このセクションは、Stripe が Webhook エンドポイントにイベントを送信する際に想定されるさまざまな動作を理解するのに役立ちます。

### 自動での再試行

本番環境では、Stripe は指数バックオフを使用して最長 3 日間、送信先へのイベントの配信を試行します。イベント送信先の**イベントの配信**タブで、該当がある場合、次回の再試行のタイミングを確認します。Stripe はサンドボックスで作成されたイベントの配信を数時間のうちに 3 回再試行します。Stripe が再試行するときに、送信先が無効化または削除されていた場合、そのイベントの以降の再試行は行われません。ただし、Stripe が再試行できるようになる前にイベントの送信先を無効にして、再び有効にした場合は、以降の再試行が引き続き行われます。

### 手動での再試行

イベントを手動で再試行するには、2 つの方法があります。

- Stripe ダッシュボードで、特定のイベントを確認しているときに **再送する** をクリックします。これは、イベント作成後最大 15 日間機能します。
- [Stripe CLI](https://docs.stripe.com/cli/events/resend) を使用して `stripe events resend <event_id> --webhook-endpoint=<endpoint_id>` コマンドを実行します。これは、イベント作成後最大 30 日間機能します。

以前に配信に失敗したイベントを Webhook エンドポイントに手動で再送信した場合、その結果のステータスコードが `2xx` になったとしても、Stripe の[自動再試行動作](https://docs.stripe.com/webhooks.md#automatic-retries)は解除されません。詳しくはこちらの[未配信の Webhookイベントを処理して今後の再試行を停止する方法](https://docs.stripe.com/webhooks/process-undelivered-events.md)をご覧ください。

### イベントの順序付け

Stripe は、イベントが生成された順序で配信されることを保証しません。たとえば、サブスクリプションを作成することで、次のイベントが生成されるとします。

- `customer.subscription.created`
- `invoice.created`
- `invoice.paid`
- `charge.created` (支払いが付随する場合)

イベントの宛先が、特定の順序でのイベントの受信に依存しないようにしてください。スナップショットイベントは `created` を秒単位で記録するため、異なるイベントが同じタイムスタンプを共有することがあります。イベントの順序や、イベントをすでに処理したかどうかを判断するために `created` を使用しないでください。代わりに [イベント ID](https://docs.stripe.com/api/events/object.md#event_object-id) を追跡して、重複した配信を特定します。API を使用して、不足しているオブジェクトを取得することもできます。たとえば、このイベントを最初に受信した場合、`invoice.paid` の情報を使用して、invoice、charge、subscription オブジェクトを取得できます。

### API のバージョン管理

イベント発生時のアカウント設定の API バージョンによって API バージョンが決まり、さらに送信先に送られる [Event (イベント)](https://docs.stripe.com/api/events.md) の構造が決まります。たとえば、アカウントで 2015-02-16 など、以前の API バージョンが設定されている場合、[バージョン管理](https://docs.stripe.com/api.md#versioning)を使用して特定のリクエストの API バージョンを変更しても、生成され送信先に送られる [Event (イベント)](https://docs.stripe.com/api/events.md) オブジェクトは、2015-02-16 の API バージョンに基づくものになります。[Event (イベント)](https://docs.stripe.com/api/events.md) オブジェクトは、作成後に変更することはできません。たとえば、支払いを更新しても、元の支払いイベントは変更されません。そのため、アカウントの API バージョンを後から更新しても、既存の [Event (イベント)](https://docs.stripe.com/api/events.md) オブジェクトがさかのぼって変更されることはありません。新しい API バージョンを使用して `/v1/events` を呼び出すことで以前の [Event (イベント)](https://docs.stripe.com/api/events.md) を取得しても、受信したイベントの構造には影響しません。テストイベントの送信先は、デフォルトの API バージョンか、最新の API バージョンのいずれかに設定できます。送信先に送られる [Event (イベント)](https://docs.stripe.com/api/events.md) は、イベントの送信先で指定されているバージョンに従って構造化されます。

## Webhook 使用のベストプラクティス

これらのベストプラクティスを見直し、Webhook エンドポイントがセキュリティで保護され、システムで適切に機能することを確認してください。

### 重複するイベントを処理する

Webhook エンドポイントは、同じイベントを複数回受信する可能性があります。処理した[イベント ID](https://docs.stripe.com/api/events/object.md#event_object-id) をログに記録し、すでにログに記録したイベントを処理しないようにすることで、重複するイベントの受信に対処することができます。

場合によっては、2 つの Event オブジェクトが個別に生成・送信されます。これらの重複を識別するには、`data.object` のオブジェクト ID と `event.type` を使用します。

### 構築済みのシステムに必要なイベントタイプのみをリッスンする

Webhook エンドポイントは、お客様の実装で必要なイベントのタイプのみを受信するように設定します。その他のイベント (またはすべてのイベント) をリッスンすると、お客様のサーバーに過度の負荷がかかるため、お勧めしません。

ダッシュボードまたは API で、Webhook エンドポイントが受信する[イベントを変更](https://docs.stripe.com/api/webhook_endpoints/update.md#update_webhook_endpoint-enabled_events)できます。

### イベントを非同期で処理する

非同期キューで受信したイベントを処理するようにハンドラを設定します。非同期でイベントを処理することを選択した場合は、拡張性の問題が発生する可能性があります。Webhook の配信が急増すると (たとえば、すべてのサブスクリプションが更新される月初など)、エンドポイントホストが対処不可能になる場合があります。

非同期キューを使用することで、同時に発生するイベントをシステムが対応できる速度で処理できるようになります。

### Webhook ルートを CSRF 保護から除外する

Rails、Django、その他のウェブフレームワークを使用している場合、貴社のサイトでは、すべての POST リクエストに 「CSRF トークン」が含まれていることを自動的に確認している可能性があります。これは、貴社とそのユーザーを[クロスサイトリクエストフォージェリ](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_\(CSRF\)) の試行から保護するための重要なセキュリティ機能です。ただし、このセキュリティ対策は貴社サイトにおける正当なイベントの処理を妨げる可能性があります。この場合は、Webhook ルートを CSRF 保護から除外しなければならない可能性があります。

#### Rails

```ruby
class StripeController < ApplicationController
  # If your controller accepts requests other than Stripe webhooks,
  # you'll probably want to use `protect_from_forgery` to add CSRF
  # protection for your application. But don't forget to exempt
  # your webhook route!
  protect_from_forgery except: :webhook

  def webhook
    # Process webhook data in `params`
  end
end
```

### HTTPS サーバーでイベントを受信する

Webhook エンドポイント (本番環境で必要) に HTTPS URL を使用する場合、Stripe は Webhook データを送信する前に、お客様のサーバーへの接続が安全であることを確認します。これを機能させるには、お客様のサーバーが、有効なサーバー証明書で HTTPS をサポートするように正しく設定されている必要があります。Stripe の Webhook は、*TLS* (TLS refers to the process of securely transmitting data between the client—the app or browser that your customer is using—and your server. This was originally performed using the SSL (Secure Sockets Layer) protocol) バージョン v1.2 および v1.3 のみサポートしています。

### エンドポイントの署名シークレットを定期的に取り消す

イベントが Stripe から送信されたことを確認する際に使用されるシークレットは、ワークベンチの [Webhook](https://dashboard.stripe.com/webhooks) タブで変更できます。シークレットを安全に保つために、定期的に、あるいは漏洩が疑われる場合に、取り消す (変更する) ことをお勧めします。

シークレットを取り消すには、以下を行います。

1. ワークベンチの [Webhook](https://dashboard.stripe.com/webhooks) タブで、取り消すシークレットの各エンドポイントをクリックします。
2. オーバーフローメニュー (⋯) にアクセスし、**シークレットの更新**をクリックします。現在のシークレットキーをただちに有効期限切れにすることも、有効期限を最大 24 時間延長して、自社のサーバーの検証コードをご自身で更新する時間を確保することもできます。この間は、エンドポイントに対して複数のシークレットキーがアクティブになります。Stripe は、有効期限までシークレットキーごとに 1 つの署名を生成します。

### イベントが Stripe から送信されたことを検証する

認証を行わないと、攻撃者が偽の webhook イベントをエンドポイントに送信して、注文の処理、アカウントアクセスの許可、レコードの変更などを不正に実行される可能性があります。Webhook イベントを処理する前に、必ず Stripe から発信されたイベントであることを確認してください。

次の両方の保護を使用します。

- **IP の許可リスト**: Stripe は webhook イベントを特定の [IP アドレス](https://docs.stripe.com/ips.md)から送信します。サーバーやファイアウォールを設定して、これらのアドレスからのリクエストのみ受け入れるようにします。
- **署名の確認**: Stripe では、`Stripe-Signature` ヘッダーに署名を含めることで、すべての Webhook イベントに署名が付与されます。この署名を[公式ライブラリ](https://docs.stripe.com/webhooks.md#verify-signature)を使用して検証するか、[手動での検証手順](https://docs.stripe.com/webhooks.md?verify=verify-manually#verify-signature)に従って、イベントが第三者によって送信または変更されていないことを確認します。

次のセクションでは、Webhook の署名を検証する方法を説明します。

1. エンドポイントのシークレットを取得します。
2. 署名を検証します。

#### エンドポイントのシークレットを取得する

ワークベンチを使用して [Webhook](https://dashboard.stripe.com/webhooks) タブに移動し、すべてのエンドポイントを表示します。シークレットを取得するエンドポイントを選択し、**クリックして表示**をクリックします。

Stripe は、エンドポイントごとに一意のシークレットキーを生成します。[テスト API キーと本番 API キー](https://docs.stripe.com/keys.md#test-live-modes)の両方に同じエンドポイントを使用する場合、シークレットはそれぞれ異なります。さらに、複数のエンドポイントを使用する場合は、署名を検証するエンドポイントごとにシークレットを取得する必要があります。この設定後、Stripe はエンドポイントに送信する各 Webhook への署名を開始します。

#### 署名の検証

#### 公式ライブラリで検証 (推奨)

### 公式ライブラリで Webhook の署名を検証する

署名を検証するには、Stripe 公式ライブラリを使用することをお勧めします。イベントペイロード、`Stripe-Signature` ヘッダー、エンドポイントのシークレットを指定して検証を実行します。検証に失敗するとエラーが返されます。

署名確認エラーが発生した場合は、[トラブルシューティング](https://docs.stripe.com/webhooks/signature.md)に関するガイドをご覧ください。

> Stripe で署名の検証を実行するには、未加工のリクエスト本文が必要です。フレームワークを使用している場合は、元の本文に手が加えられないようにする必要があります。 未加工のリクエスト本文に何らかの変更が行われた場合、検証は失敗します。

#### Ruby

```ruby

# Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
# Find your keys at https://dashboard.stripe.com/apikeys.
client = Stripe::StripeClient.new('<<YOUR_SECRET_KEY>>')

require 'stripe'
require 'sinatra'

# If you are testing your webhook locally with the Stripe CLI you
# can find the endpoint's secret by running `stripe listen`
# Otherwise, find your endpoint's secret in your webhook settings in
# the Developer Dashboard
endpoint_secret = 'whsec_...'

# Using the Sinatra framework
set :port, 4242

post '/my/webhook/url' do
  payload = request.body.read
  sig_header = request.env['HTTP_STRIPE_SIGNATURE']
  event = nil

  begin
    event = Stripe::Webhook.construct_event(
      payload, sig_header, endpoint_secret
    )
  rescue JSON::ParserError => e
    # Invalid payload
    puts "Error parsing payload: #{e.message}"
    status 400
    return
  rescue Stripe::SignatureVerificationError => e
    # Invalid signature
    puts "Error verifying webhook signature: #{e.message}"
    status 400
    return
  end

  # Handle the event
  case event.type
  when 'payment_intent.succeeded'
    payment_intent = event.data.object # contains a Stripe::PaymentIntent
    puts 'PaymentIntent was successful!'
  when 'payment_method.attached'
    payment_method = event.data.object # contains a Stripe::PaymentMethod
    puts 'PaymentMethod was attached to a Customer!'
  # ... handle other event types
  else
    puts "Unhandled event type: #{event.type}"
  end

  status 200
end
```

#### 手動で検証

### 手動で Webhook の署名を検証する

公式ライブラリを使用して Webhook イベントの署名を検証することをお勧めしますが、このセクションに従ってカスタムソリューションを作成することもできます。

署名付きイベントに含まれる `Stripe-Signature` ヘッダーには、タイムスタンプと、検証が必要な署名が 1 つ以上含まれています。タイムスタンプのプレフィックスには `t=` が指定され、署名のプレフィックスには_スキーム_が指定されます。スキームは `v` で始まり、その後に整数が続きます。現在、本番環境で有効な署名スキームは `v1` のみです。テストを支援するために、Stripe はテストイベント用に偽の `v0` スキームで追加の署名を送信します。

```
Stripe-Signature:
t=1492774577,
v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd,
v0=6ffbb59b2300aae63f272406069a9788598b792a944a07aba816edb039989a39
```

> わかりやすくするために改行を使用していますが、実際の `Stripe-Signature` ヘッダーは 1 行です。

Stripe は、[SHA-256](https://en.wikipedia.org/wiki/SHA-2) のハッシュベースのメッセージ認証コード ([HMAC](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code)) を使用して署名を生成します。[ダウングレード攻撃](https://en.wikipedia.org/wiki/Downgrade_attack)を防ぐには、`v1` 以外のスキームをすべて無視します。

[エンドポイントのシークレットを取り消す](https://docs.stripe.com/webhooks.md#roll-endpoint-secrets)際、同じスキームとシークレットのペアを持つ複数の署名を存在させ、以前のシークレットを最長 24 時間アクティブにしておくことができます。この間、エンドポイントには複数のアクティブなシークレットが存在し、Stripe はシークレットごとに署名を 1 つ生成します。

署名を検証するために手動のソリューションを作成するには、以下のステップを完了させる必要があります。

#### ステップ 1: ヘッダーからタイムスタンプと署名を抽出する

要素のリストを取得するには、`,` 文字を区切り文字として使用してヘッダーを分割します。次に、各要素を `=` 文字を区切り文字として使用して区切り、接頭語と値のペアを取得します。

接頭語 `t` の値はタイムスタンプに対応し、`v1` は署名 (複数可) に対応します。他のすべての要素は破棄できます。

#### ステップ 2: `signed_payload` 文字列を準備する

`signed_payload` 文字列は以下を連結することで作成されます。

- タイムスタンプ (文字列として)
- 文字 `.`
- 実際の JSON ペイロード (リクエスト本文)

#### ステップ 3: 想定される署名を決定する

SHA256 ハッシュ関数を使用して HMAC を計算します。エンドポイントの署名シークレットをキーとして使用し、`signed_payload` 文字列をメッセージとして使用します。

#### ステップ 4: 署名を比較する

ヘッダー内の署名 (1 つまたは複数) を想定される署名と比較します。一致する場合、現在のタイムスタンプと受信したタイムスタンプの差を計算し、その差が許容範囲内かどうかを判断します。

タイミング攻撃から保護するには、一定時間の文字列比較を使用して、想定される署名を受信した各署名と比較します。

### リプレイ攻撃を防止する

[リプレイ攻撃](https://en.wikipedia.org/wiki/Replay_attack)とは、攻撃者が有効なペイロードとその署名を傍受し、それを再送信することを言います。そのような攻撃を低減するために、Stripe は `Stripe-Signature` ヘッダーにタイムスタンプを含めています。このタイムスタンプは署名されたペイロードの一部であるため、署名によっても検証され、攻撃者は署名を無効にしなければタイムスタンプを変更できません。署名が有効でもタイムスタンプが古すぎる場合は、アプリケーションにペイロードを拒否させることができます。

Stripe のライブラリには、タイムスタンプと現在時刻の間に 5 分のデフォルトの許容範囲があります。この許容範囲は、署名を検証する際に追加のパラメーターを指定することで変更できます。ネットワークタイムプロトコル ([NTP](https://en.wikipedia.org/wiki/Network_Time_Protocol)) を使用して、サーバーのクロックが正確であり、Stripe のサーバーの時間と同期していることを確認します。

> 許容値 `0` は使用しないでください。許容値 `0` を使用すると、最新性チェックが完全に無効になります。

Stripe は、イベントをエンドポイントに送信するたびにタイムスタンプと署名を生成します。Stripe がイベントを再試行する場合 (たとえば、その前にエンドポイントが `2xx` 以外のステータスコードで応答した場合)、新しい配信試行に対して新しい署名とタイムスタンプを生成します。

### 2xx レスポンスを素早く返す

[エンドポイント](https://docs.stripe.com/webhooks.md#example-endpoint)は、タイムアウトを引き起こす可能性のある複雑なロジックが実行される前に、成功のステータスコード (`2xx`) を素早く返す必要があります。たとえば、会計システムで顧客の請求書を支払い済みとして更新する前に、`200` のレスポンスを返してください。

## See also

- [Amazon EventBridge にイベントを送信](https://docs.stripe.com/event-destinations/eventbridge.md)
- [Azure Event Grid にイベントを送信する](https://docs.stripe.com/event-destinations/eventgrid.md)
- [シンイベントタイプのリスト](https://docs.stripe.com/api/v2/core/events/event-types.md)
- [スナップショットイベントタイプの一覧](https://docs.stripe.com/api/events/.md)
- [インタラクティブな Webhook エンドポイントビルダー](https://docs.stripe.com/webhooks/quickstart.md)
