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

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

HTTPS Webhook エンドポイントを作成してイベントを受信できます。Webhook エンドポイントを登録すると、Stripe アカウントで[イベント](https://docs.stripe.com/events/how-events-work.md#event-formats)が発生したときに、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 つのエンドポイントを登録して作成し、同時に複数の異なるイベントタイプを処理することも、特定のイベント用に個々のエンドポイントを設定することもできます。

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

Stripe がイベントの配信先を認識できるように、[EventDestinations](https://docs.stripe.com/api/v2/event-destinations.md) または[ワークベンチの Webhook タブ](https://dashboard.stripe.com/webhooks)を使用して、アクセス可能な URL を持つ Webhook エンドポイントを作成して登録します。Stripe には最大 16 個のイベントの送信先を登録できます。登録する 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://example.com` であり、Webhook エンドポイントへのルートが `/webhook-handler` である場合は、エンドポイント URL として `https://example.com/webhook-handler` を指定します。

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

#### ダッシュボード

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

1. ワークベンチの [Webhook](https://dashboard.stripe.com/webhooks) タブを開きます。
2. **イベント送信先を作成**をクリックします。
3. **アカウント**を選択して、自社のアカウントからのイベントを受信します。
4. ペイロード形式として、**Thin** (推奨) または **Snapshot** を選択します。スナップショットの送信先の場合は、API バージョンも選択します。
5. 受信するイベントタイプを選択します。利用可能なタイプについては、[シンイベントカタログ](https://docs.stripe.com/api/v2/core/events/event-types.md)または[スナップショットイベントカタログ](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)際に使用します。

#### 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-10-28.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/events/how-events-work.md#event-formats)をご覧ください。

自社のアカウントからの[シンイベント](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-10-28.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 エンドポイントにイベントを転送する](https://docs.stripe.com/cli/listen)ことで、Webhook をローカルでテストできます。

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/events/how-events-work.md#event-formats)をローカルリスナーに転送します。

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

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

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

   ```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/events/manage-webhook-endpoints.md#signature-errors)方法を参照してください。

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

このコードスニペットは、Webhook イベントの送信先を通じてイベントを受信し、指定されたイベントを処理して、成功を示す `2xx` レスポンスを返すように設定された Webhook 関数です。ほとんどの連携において、新規の実装には [シン](https://docs.stripe.com/events/how-events-work.md#event-formats) イベントハンドラーを使用します。サードパーティのツールで完全な `Event` オブジェクトのペイロードが必要な場合や、追加の API コールなしで `previous_attributes` を取得する必要がある場合は、[スナップショット](https://docs.stripe.com/events/how-events-work.md#event-formats)イベントハンドラーを使用してください。

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

スナップショットイベントハンドラを作成するときは、イベントの `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}"
      halt 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/events/how-events-work.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/events/how-events-work.md#event-formats)をローカルリスナーに転送します。

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

#### シンイベント

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

```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)でこのイベントタイプを処理する必要があります。

#### コンテキストの使用

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

このコードスニペットは、受信したイベントを確認し、該当する場合は元の口座を検出して、イベントを処理し、`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/events/manage-webhook-endpoints.md#process-undelivered-events)を参照してください。

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

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. オーバーフローメニュー (⋯) を開き、**シークレットの更新**をクリックします。現在のシークレットは、すぐに失効させることも、サーバーの検証コードを更新する時間を確保するために最大 7 日間有効なままにすることもできます。この間、エンドポイントでは複数のシークレットが有効になります。Stripe は、失効するまで各シークレットに対して 1 つずつ署名を生成します。

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

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

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

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

次のセクションでは、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/events/manage-webhook-endpoints.md#signature-errors)を参照してください。リクエスト本文が変更されていないかを調べるために、SDK のエラーで `Stripe-Signature` ヘッダーの `payload` エントリーが参照されることがあります。このエントリーは署名対象外のデバッグメタデータです。リクエストの認証とペイロードの整合性の検証に使用できるのは、有効な `v1` HMAC 署名のみです。

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

#### Ruby

```ruby
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 つ以上の署名がカンマ区切りで含まれています。以下の例では読みやすさを重視して改行していますが、実際に受信するヘッダーは 1 行です。

```
Stripe-Signature:
t=1798765432,
v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd,
v1=38c9f77a6fdb86d7f67e427e6076f1e38e244eb8ddc7a90f7f9dd4478d5bb2c2,
v0=6ffbb59b2300aae63f272406069a9788598b792a944a07aba816edb039989a39,
secret=whsec_****************************PzVt,
destination=we_1TN7mG6oEAX7HoXLZoZugElv,
payload=a25aefc6
```

各署名には、スキームを示すプレフィックスが付いています。スキームは `v` に整数を続けた形式です。現在、本番環境で有効な署名スキームは `v1` のみです。テスト用として、Stripe はサンドボックスのイベントに対し、ダミーの `v0` スキームの署名も送信します。

デバッグ用として、Stripe が任意の `secret`、`destination`、`payload` エントリーを追加する場合があります。実装では未知のエントリーも受け入れ、エントリーの数や順序が固定されていることを前提にしないでください。

| エントリー | 説明 |
| --- | --- |
| `t` | Stripe がこのイベントに署名した日時を、Unix エポックからの経過秒数で表すタイムスタンプです。イベント自体の生成日時を示す値ではないため、署名の検証にのみ使用してください。 |
| `v0` | サンドボックスアカウントに関連するイベントにのみ含まれる署名の値です。署名の検証には使用できません。コードでは、このタイプのエントリーを無視する必要があります。 |
| `v1` | [SHA-256](https://en.wikipedia.org/wiki/SHA-2) を使ったハッシュベースのメッセージ認証コード ([HMAC](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code)) によって生成される署名です。計算には、イベントのペイロード、タイムスタンプ、エンドポイントの署名シークレットを使用します。同じヘッダーに複数含まれる場合があります。少なくとも 1 つの `v1` エントリーが、計算で求めた署名と一致することを検証してください。 |
| `secret` | エンドポイントで有効な最新の署名シークレットの一部を伏せ字にした値です。Stripe が使用する署名シークレットと、コードで使用している値が一致していない場合の調査に役立ちます。 |
| `destination` | 署名シークレットが属するエンドポイントの ID で、`we_` または `ed_` で始まります。この値を使うと、ダッシュボードで該当するエンドポイントと署名シークレットを探せます。 |
| `payload` | 署名された HTTP リクエスト本文そのものから計算した SHA-256 ダイジェストを、16 進数で表した値の末尾部分です。署名を計算する前にコードで本文を変更していないかを調べる目的にのみ使用してください。 |

> #### デバッグメタデータを検証に使用しない
> 
> `secret`、`destination`、`payload` の各エントリーは、HMAC の計算対象には含まれません。攻撃者が偽造リクエストに合うデバッグ値を作成できるため、リクエストの認証やペイロードの整合性の検証には使用しないでください。リクエストの認証とペイロードの整合性の検証に使用できるのは、有効な `v1` 署名のみです。

[ダウングレード攻撃](https://en.wikipedia.org/wiki/Downgrade_attack)を防ぐため、`v1` 以外の署名スキームは無視し、検証には `t` と `v1` のエントリーのみを使用してください。

[エンドポイントの署名シークレットを更新](https://docs.stripe.com/webhooks.md#roll-endpoint-secrets)してから、以前のシークレットを最大 7 日間有効にしておく場合、同じスキームの署名が複数含まれることがあります。この間、エンドポイントでは複数のシークレットが有効になり、Stripe はシークレットごとに 1 つの署名を生成します。少なくとも 1 つの `v1` エントリーが有効であることを検証してください。

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

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

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

プレフィックスが `t` の値はタイムスタンプを、`v1` の各値は署名を表します。タイムスタンプとすべての `v1` の値を保持し、デバッグメタデータを含め、それ以外の要素は署名の計算対象から除外してください。

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

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

- 文字列として表したタイムスタンプの値
- 文字 `.`
- 実際の JSON ペイロード (変更を加えていない HTTP リクエスト本文)

連結する前に、ペイロードの整形や変更を行わないでください。たとえば、`signed_payload` は `1798765432.{` で始まる場合があります。

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

SHA-256 ハッシュ関数で HMAC を計算します。キーにはエンドポイントで有効な署名シークレットを、メッセージには `signed_payload` 文字列を使用します。

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

定数時間の文字列比較を使い、計算で求めた署名をヘッダー内のすべての `v1` 署名と比較します。いずれかが一致した場合は、現在のタイムスタンプと受信したタイムスタンプの差を計算し、許容範囲を外れていればリクエストを拒否します。

どの署名も一致しない場合は、リクエストを拒否します。

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

[リプレイ攻撃](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

- [シンイベントタイプのリスト](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)
