# 在 Webhook 端点中接收 Stripe 事件

在您的 Webhook 端点监听来自 Stripe 的事件，即可让您的集成自动触发响应。

您可以创建 HTTPS Webhook 端点以接收事件。注册 Webhook 端点后，当您的 Stripe 账户中发生[事件](https://docs.stripe.com/event-destinations.md#events-overview)时，Stripe 会将实时数据推送到该端点。Stripe 使用 HTTPS 将 Webhook 事件作为包含事件信息的 JSON 负载发送到您的应用程序。

接收 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 事件。您可以注册并创建一个端点来同时处理几种不同类型的事件，或者为特定事件设置单独的端点。

## 设置您的端点

使用 Workbench 中的 [API](https://docs.stripe.com/api/v2/event-destinations.md) 或 [Webhook](https://dashboard.stripe.com/webhooks) 标签页注册您 Webhook 端点的可访问 URL，以便 Stripe 知道将事件发送到哪里。您最多可以向 Stripe 注册 16 个 Webhook 端点。已注册的 Webhook 端点必须是可公开访问的 HTTPS URL。

- 如果您有 localhost 服务器，但没有可公开访问的 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'])`，则将 `https://mycompanysite.com/stripe_webhooks` 指定为端点 URL。

### 为 Webhook 端点创建事件接收端

#### 管理平台

在管理平台中创建新的 Webhook 端点：

1. 打开 Workbench 中的 [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. 点击**继续**，然后提供**端点 URL** 和 Webhook 的可选描述。
8. 在 Webhook 设置页面上，将显示以 `whsec_` 开头的签名密钥。点击**显示密钥**，并复制该值以便在[创建处理程序](https://docs.stripe.com/webhooks.md#webhook-endpoint-def)时使用。

> [Workbench](https://docs.stripe.com/workbench.md) 替换了现有的[开发者管理平台](https://docs.stripe.com/development/dashboard.md)。您仍然可以在开发者管理平台中[创建新的 Webhook 端点](https://docs.stripe.com/development/dashboard/webhooks.md)，尽管我们建议使用 Workbench。

#### 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 [将事件转发到本地端点](https://docs.stripe.com/cli/use-cli#forward-events-to-your-local-webhook-endpoint)来在本地测试 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/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` 端点的 localhost 网站，您可以在[创建处理程序](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)
   ```

> 要在 `stripe listen` 中使用 `--forward-to` 参数，您必须在终端中通过 [Stripe CLI](https://docs.stripe.com/cli.md) 运行该命令。此命令无法在 [Workbench Shell](https://docs.stripe.com/workbench/shell.md) 中运行，因为它不支持 `--forward-to` 参数。

## 创建处理程序

设置一个 HTTP 或 HTTPS 端点函数，该函数可以用 POST 方法接受 Webhook 请求。如果您仍在您的本地机器上开发您的端点，则它可以使用 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)。

#### 示例端点

此代码片段是一个 Webhook 函数，配置它的目的是检查从 Stripe 账户接收的事件、处理事件并返回 `200` 响应。使用 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
```

#### 精简事件处理程序（Clover+ 版）

创建精简事件处理程序时，请使用 `fetchRelatedObject()` 方法检索与事件关联对象的最新版本。事件可能包含需要通过 `EventNotification` 上的 `.fetchEvent()` 实例方法才能获取的[附加数据](https://docs.stripe.com/event-destinations.md#fetch-data)。该等数据的具体结构取决于`事件类型`。

事件类型必须在版本发布时完成部署，才能确保其在该版本 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)时，请选择该接收端要监听的范围：

#### 管理平台

- **您的账户**：您账户中资源产生的事件。
- **Connect 子账户**：属于您 Connect 子账户的资源产生的事件。

#### 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)：属于您 Connect 子账户的资源产生的事件。

为 **Connect 子账户**注册事件接收端，以接收属于您 Connect 子账户资源的事件。示例包括[直接收款](https://docs.stripe.com/connect/direct-charges.md)、Connect 子账户的客户和支付方式、提现失败以及旧版快照 Connect 子账户生命周期更新，例如入驻、验证、外部账户更改和账户断开。对于 Accounts v2，此范围仅接收代表您 Connect 子账户的 Account 对象的快照事件。

根据您的 Connect 集成，您可能还需要为**您的账户**注册事件接收端。原因包括：

- 处理平台账户中资源产生的事件，包括平台客户、平台拥有的收款、[定向收款](https://docs.stripe.com/connect/destination-charges.md)，以及[独立收款和转账](https://docs.stripe.com/connect/separate-charges-and-transfers.md)。
- 处理与代表 Connect 子账户的 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)从 Connect 子账户转发到您的本地监听器。

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

#### 精简事件

使用以下命令将[精简事件](https://docs.stripe.com/event-destinations.md#events-overview)从 Connect 子账户转发到您的本地监听器。

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

## Optional: 为 Organizations 创建事件接收端

当您作为 Organization [创建事件接收端](https://docs.stripe.com/webhooks.md#create-webhook-endpoint)时，您可以选择它要监听的范围：

#### 管理平台

- **您组织中的账户**：您组织账户中的资源产生的事件。
- **Connect 子账户**：您组织 Connect 子账户内部的资源产生的事件。

#### 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)：您组织 Connect 子账户内部的资源产生的事件。

### 组织事件接收端不支持的事件类型行为

Stripe 以异步方式发送大多数事件类型，但对于某些事件类型，会等待响应。在这些情况下，Stripe 会根据事件接收端是否响应而采取不同的处理方式。

如果您的事件接收端接收[组织](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](https://docs.stripe.com/payments/checkout.md) 或将客户重定向到 Stripe 托管的支付页面时，接收 `checkout_sessions.completed` 的组织目标无法[处理重定向行为](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`

#### 快照事件

此代码片段是一个 Webhook 函数，配置它的目的是检查接收的事件、检测原始账户（适当时）、处理事件并返回 `200` 响应。

#### 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
```

#### 精简事件处理程序（Clover+ 版）

使用 `EventNotification` 的 `context` 属性来确定 [organization](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 端点没有接收到您期望接收的事件。

### 查看事件的发送情况

要查看事件投递，请打开 [Workbench](https://docs.stripe.com/workbench.md)，选择**Webhooks**下的 Webhook 端点，然后选择**事件投递**标签页。**事件投递**标签页会列出事件列表，并显示它们是 `Delivered`、`Pending` 还是 `Failed`。点击事件，可查看元数据，包括本次投递尝试的 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 错误）错误 | 我们无法与目标服务器建立安全连接。目标服务器的证书链中的 SSL/TLS 证书或中间证书的问题通常会导致这些错误。Stripe 需要 `v1.2` 或更高版本的 *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)。 | 执行 [SSL 服务器测试](https://www.ssllabs.com/ssltest/)，找出可能导致此错误的问题。 |
| （超时） 错误 | 目标服务器响应 Webhook 请求的时间过长。 | 务必要在您的 Webhook 处理代码中延迟复杂的逻辑并立即返回成功的响应。 |

## 事件的发送行为

本节旨在帮助您理解 Stripe 向 Webhook 端点发送事件时的不同行为。

### 自动重试

在真实模式下，我们会尽量连续三天一直尝试将事件发送到您的接收端，但尝试频率递减。在事件接收端的**事件发送**选项卡中查看下次重试的时间（如果适用）。我们在几个小时内重试了三次在沙盒中创建的事件发送操作。如果在我们重新尝试时您的接收端已被禁用或删除，我们将阻止您再次尝试该事件。但是，如果您在我们重试之前禁用并又重新启用事件接收端，则仍可以看到今后的重试尝试。

### 手动重试

有两种方法可以手动重试事件：

- 在 Stripe 管理平台中，查看特定事件时点击**重新发送**。这适用于创建事件后的 15 天内。
- 用 [Stripe CLI](https://docs.stripe.com/cli/events/resend) 运行 `stripe events resend <event_id> --webhook-endpoint=<endpoint_id>` 命令。这适用于创建事件后的 30 天内。

手动向 Webhook 端点重新发送之前发送失败的事件并不会解除 Stripe 的[自动重试行为](https://docs.stripe.com/webhooks.md#automatic-retries)，即使返回的是 `2xx` 状态码。了解如何[处理未送达的 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` 事件，则可以通过该事件中的信息去调用 API 获取账单、收款和订阅对象。

### API 版本控制

事件发生时您的账户设置中的 API 版本表示 API 版本，因此会将 [Event](https://docs.stripe.com/api/events.md) 的结构发送到您的目的地。例如，如果您的账户设置为旧的 API 版本，如 2015-02-16，并且您通过 [versioning](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)，然后不处理已记录的事件来防止接收重复事件。

在某些情况下，会生成并发送两个单独的 Event 对象。要识别这些重复项，请使用 `data.object` 中对象 ID 和 `event.type`。

### 仅侦听集成所需的事件类型

将 Webhook 端点配置为仅接收集成所需的事件类型。侦听额外的事件（或所有事件）会给您的服务器带来过度的压力，因此不建议这样做。

您可以在管理平台或通过 API [更改事件](https://docs.stripe.com/api/webhook_endpoints/update.md#update_webhook_endpoint-enabled_events)（Webhook 端点收到的事件）。

### 异步处理事件

配置您的处理程序来处理带有异步队列的传入事件。如果选择同步处理事件，可能会遇到扩展性问题。Webhook 发送量的任何大峰值（例如，在所有订阅都续订的月初）都可能会使您的端点主机不堪重负。

异步队列可让您以系统支持的速度处理并发事件。

### 去除 Webhook 路由的 CSRF 保护

如果您正在使用 Rails、Django 或其他网络框架，则您的站点可能会自动检查每个 POST 请求是否包含一个_CSRF 令牌_。这是一个重要的安全功能，有助于保护您及您的用户免受[跨站请求伪造 (cross-site request forgery, CSRF)](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_\(CSRF\)) 攻击。但是，这一安全措施也可能会阻止您的站点处理合法事件。如果是这样，则您可能需要从 CSRF 保护中去除 Webhook 路由。

#### 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 的密钥可以在 Workbench 的 [Webhook](https://dashboard.stripe.com/webhooks) 标签页中进行修改。为了确保它们的安全，我们建议您定期滚动（更改）密钥，或者在您怀疑密钥遭到泄露时执行此操作。

要滚动私钥：

1. 在 Workbench 的 [Webhook](https://dashboard.stripe.com/webhooks) 标签页中，点击要为其滚动密钥的每个端点。
2. 导航到溢出菜单 (⋯)，然后点击**滚动私钥**。您可以选择立即使当前私钥过期，也可以延迟 24 小时后再让它过期，以便让自己有时间更新服务器上的验证码。这段时间内，端点会有多个密钥处于活动状态。Stripe 会为每个私钥生成一个签名，直到过期。

### 验证事件是否是从 Stripe 发送的

如果未验证，攻击者可能会向您的端点发送伪造的 Webhook 事件，从而触发诸如履行订单、授予账户访问权限或修改记录等操作。在根据 Webhook 事件采取行动之前，请务必验证这些事件是否源自 Stripe。

请同时采用以下两种保护措施：

- **IP 地址白名单**：Stripe 会从一组固定的 [IP 地址](https://docs.stripe.com/ips.md) 发送 Webhook 事件。请配置您的服务器或防火墙，使其仅接受来自这些地址的 Webhook 请求。
- **签名验证**：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. 验证签名。

#### 检索您的端点的密钥

使用 Workbench 并转到 [Webhook](https://dashboard.stripe.com/webhooks) 标签页以查看您的所有端点。选择要为其获取密钥的端点，然后点击**点击以显示**。

Stripe 为每个端点生成一个唯一私钥。如果您为[测试和真实 API 私钥](https://docs.stripe.com/keys.md#test-live-modes)使用相同的端点，则每个端点的私钥各不相同。此外，如果您使用多个端点，则必须要为您想用来验证签名的每个端点都获取一个私钥。有了这种设置，Stripe 便可开始对它发送到端点的每个 Webhook 进行签名。

#### 验证签名

#### 用官方库验证（推荐）

### 用官方库验证 Webhook 签名

我们建议使用我们的官方库来验证签名。通过提供事件的有效载荷、`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` 头都有一个时间戳和一个或多个签名，必须对其进行验证。时间戳有一个 `t=` 前缀，每个签名都有一个 *scheme* 前缀。方案以 `v` 开头，后跟一个整数。目前，唯一有效的真实签名方案是 `v1`。为辅助测试，Stripe 会为测试模式下的事件发送一个使用虚假 `v0` 方案的附加签名。

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

> 为清晰起见，我们已经添加了新行，而真正的 `Stripe-Signature` 头在单独一行。

Stripe 用基于散列的消息验证码([HMAC](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code))和 [SHA-256](https://en.wikipedia.org/wiki/SHA-2)生成签名。为防止[降级攻击 (Downgrade attack)](https://en.wikipedia.org/wiki/Downgrade_attack)，请忽略所有不是 `v1` 的方案。

当您[滚动某个端点的密钥](https://docs.stripe.com/webhooks.md#roll-endpoint-secrets)时，您可以有具有相同方案和密钥对的多个签名，并且可以使前一个密钥的活动状态保持长达 24 小时。在此期间，您的端点会有多个有效的密钥，Stripe 则为每个密钥生成一个签名。

要创建手动的签名验证解决方案，您必须完成以下步骤：

#### 第 1 步：从标头提取时间戳和签名

拆分标头，用 `,` 字符作为分隔符，获得元素列表。然后分割每个元素，使用 `=` 字符作为分隔符，获得前缀和值对。

前缀 `t` 的值对应于时间戳，`v1` 对应于签名（一个或多个）。您可以丢弃所有其他元素。

#### 第 2 步：准备 `signed_payload` 字符串

`signed_payload` 字符串是通过连接以下内容创建的：

- 时间戳（字符串形式）
- 字符 `.`
- 实际的 JSON 有效载荷（即请求体）

#### 第 3 步：确定期望的签名

用 SHA256 散列函数计算 HMAC。用端点的签名密钥作为密钥，同时用 `signed_payload` 字符串作为消息。

#### 第 4 步：对比签名

将头内的签名与期望的签名进行比较。为进行相等匹配，应计算当前时间戳和收到的时间戳之间的差异，然后确定它是否在您的容差范围内。

为了防止定时攻击，请用恒定时间字符串比较方式将期望的签名与收到的每个签名进行比较。

### 防止重放攻击

[重放攻击 (Replay Attack)](https://en.wikipedia.org/wiki/Replay_attack)是攻击者截获有效的有效载荷及其签名，然后再重新传输。为了减轻这类攻击，Stripe 在 `Stripe-Signature` 头内包含了时间戳。因为此时间戳是已签名的有效载荷的一部分，因此它也由签名验证，所以攻击者在不使签名无效的情况下不能更改时间戳。如果签名有效，但时间戳太旧，则您可以让您的应用程序拒绝有效载荷。

我们的库在时间戳和当前时间之间有 5 分钟的默认容差。您可以通过在验证签名时提供附加参数来更改此容差。使用网络时间协议（[Network Time Protocol，简称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)
