# How events work

Learn how Stripe generates events and how they're delivered to your integration.

When a state change occurs in your Stripe account, Stripe generates an event and can push it to one or more configured destinations. Your application receives the event, identifies the type, and takes the appropriate action.

This page describes how events are structured and delivered. For destination-specific setup instructions, see:

- [Webhook endpoints](https://docs.stripe.com/webhooks.md)
- [Amazon EventBridge](https://docs.stripe.com/event-destinations/eventbridge.md)
- [Azure Event Grid](https://docs.stripe.com/event-destinations/eventgrid.md)

## Event generation

Every state change in Stripe that your integration might need to respond to produces an `Event` [object](https://docs.stripe.com/api/v2/core/events.md). A single action against a Stripe resource can result in multiple events. For example, creating a new subscription can produce both a `customer.subscription.created` event and a `payment_intent.succeeded` event under certain conditions.

Events are delivered asynchronously to your configured event destinations. You can configure each destination to receive a specific set of event types. The format and delivery of each event depend on your destination configuration.

## Event destinations 

Stripe pushes events to your configured destinations, which can be [webhook endpoints](https://docs.stripe.com/webhooks.md), [Amazon EventBridge](https://docs.stripe.com/event-destinations/eventbridge.md), or [Azure Event Grid](https://docs.stripe.com/event-destinations/eventgrid.md). Your application receives these events so it can run backend actions, such as:

- Sending users a notification when a customer confirms a payment
- Initiating an internal claims reconciliation process when a customer disputes a charge
- Granting access to your user when they make successful recurring subscription payments

Each destination receives events in one of two formats: [thin](https://docs.stripe.com/events/how-events-work.md#thin-events) or [snapshot](https://docs.stripe.com/events/how-events-work.md#snapshot-events). You choose the format when you create the destination.

### Manage event destinations

To create, update, delete, or disable an event destination in the Dashboard, open the [Webhooks](https://dashboard.stripe.com/webhooks) tab in Workbench or use the [event destinations API](https://docs.stripe.com/api/v2/event-destinations/.md). After you disable an event destination, Stripe stops sending any events to that destination. After you re-enable a destination, Stripe resumes sending events to it.

For detailed, destination-specific setup instructions, see:

- [Webhook endpoints](https://docs.stripe.com/webhooks.md)
- [Amazon EventBridge](https://docs.stripe.com/event-destinations/eventbridge.md)
- [Azure Event Grid](https://docs.stripe.com/event-destinations/eventgrid.md)

### Event destination limits

You can register a maximum of 16 event destinations per livemode or sandbox account. When registering a snapshot event destination with a version different from your account’s default API version, you can register up to three uniquely versioned snapshot event destinations.

## Event formats 

Stripe delivers events in two formats:

- **[Thin events](https://docs.stripe.com/events/how-events-work.md#thin-events)**: Stripe sends a lightweight notification containing the event type and resource ID. Your code can then optionally fetch the complete event or the latest state of the related resource from the API.
- **[Snapshot events](https://docs.stripe.com/events/how-events-work.md#snapshot-events)**: Stripe sends a payload containing the complete `Event` object, including a point-in-time snapshot of the related resource. Your code can optionally fetch the latest state of the related resource from the API.

### Comparison 

For new integrations, use thin events. Thin events are unversioned, fully typed in the Stripe SDKs, and let your integration fetch the latest resource state. The notification itself carries identifiers and event metadata, not the resource state, so your code retrieves the current resource from the API.

Use thin events when:

- Data integrity is critical, and your application must act on the most up-to-date information.
- You want to simplify versioning by managing upgrades only on the client side.
- You’re building a modern, type-safe application and want to take advantage of SDK typing benefits.

Use snapshot events when:

- A third-party tool or integration requires receiving the complete `Event` object payload.
- You need to audit the specific fields that changed without making a subsequent API call. Thin events include changed fields in the `changes` hash on the complete event, while snapshot events carry `previous_attributes` directly in the payload.
- Your integration requires a point-in-time view of the resource definition and can tolerate working with eventually-consistent data.

The following table outlines the key differences between the two formats.

| Characteristics | Snapshot events | Thin events |
| --- | --- | --- |
| Created by | API v1 and API v2 resource state changes | API v1 and API v2 resource state changes. See the [thin event catalog](https://docs.stripe.com/api/v2/core/events/event-types.md) for the full list of supported types. |
| Delivered payload | **Large**: Includes a snapshot of the API object related to the event | **Small**: Includes an ID of the API object related to the event in a lightweight event notification |
| Accessing additional data | Fetch the latest object from the API. The object in the event payload might be outdated by the time you process the event. | Fetch the latest object from the API or retrieve the complete [event](https://docs.stripe.com/api/v2/events.md) from `v2/events`. The complete event payload can include extra details. For example, the payload for a `v1.billing.meter.error_report_triggered` event includes information about the types and frequency of errors raised. |
| SDK typing | Untyped | Typed |
| Versioning | Versioned by API version | Unversioned, allowing you to upgrade your integration without changing your event destination configuration |
| API to view events | [Events v1 API](https://docs.stripe.com/api/events.md) | [Events v2 API](https://docs.stripe.com/api/v2/events.md) |

If you use [Accounts v2](https://docs.stripe.com/connect/accounts-v2/migrate-integration.md), you might listen to both snapshot and thin events.

### Thin events 

A thin event sends a lightweight notification containing only identifying information:

- the event type (`type`), which describes what happened. For example, `v2.core.account.updated`. See the [full list of thin events](https://docs.stripe.com/api/v2/core/events/event-types.md)
- the event ID (`id`), which you use to retrieve the complete `Event` object.
- a timestamp (`created`), which records when the event occurred.
- the ID of the related resource (`related_object`), alongside that resource’s type and API URL.

To access the full resource state or additional event data, your code needs to make a subsequent API call using this information. For the complete set of properties, see the [Event object](https://docs.stripe.com/api/v2/core/events/object.md).

Both API v1 and API v2 resources generate thin events. Because thin event notification payloads are unversioned, you can upgrade your Stripe SDK or your integration’s API version without changing your event destination configuration. Events and resources that you fetch from the API still follow standard [API versioning behavior](https://docs.stripe.com/api/versioning.md). Notifications are also fully typed in the SDKs, so your code gets compile-time type safety when it processes events. These properties make thin events the recommended format for new integrations.

#### Notification payload structure 

The following example shows a `v2.core.account.updated` thin event notification. The `id` property contains the ID of the associated `Event` object. The `reason` hash describes what triggered the event, and the `related_object` hash identifies the associated resource.

```json
{
  "id": "evt_test_65UIRNU7G1XbhCfOim416TgmEI4ASQ3jHxXt8RFwXoeVwO",
  "object": "v2.core.event",
  "type": "v2.core.account.updated",
  "livemode": false,
  "created": "2026-03-09T13:00:28.435Z",
  "context": null,
  "reason": {
    "type": "request",
    "request": {
      "id": "req_v2y9y15XqG3Futmjg",
      "idempotency_key": "ik_TgmEI3jHxXt8RFw4jS7ve2QcAReDQWBjPAkAEUm"
    }
  },
  "related_object": {
    "id": "acct_1T93Q4Pmpb34Vto6",
    "type": "v2.core.account",
    "url": "/v2/core/accounts/acct_1T93Q4Pmpb34Vto6"
  }
}
```

#### Processing thin event notifications 

The initial notification contains minimal data. Depending on your use case, you can get more information about the event or object or act on the notification immediately:

- **Retrieve the complete event**: Use the SDK or API to [retrieve the event](https://docs.stripe.com/api/v2/core/events/retrieve.md) object. The complete object includes two types of additional data:

  - Contextual information about the event in the `data` hash. For example, a `v1.billing.meter.error_report_triggered` event includes details about validation error types and summaries.
  - The previous values of any attributes that changed on the resource in the `changes` hash.

- **Retrieve the latest state of the related object**: Use the [related_object.url](https://docs.stripe.com/api/v2/core/events/object.md#v2_event_object-related_object-url) to get the latest version of the resource associated with the event through the SDK or API.

- **Process the notification immediately**: If the event type and resource ID in the notification provide enough information for your use case, you can process it without making an additional API call.

The following table shows which properties are available in the event notification versus the complete event object:

| Property name | Event notification | Event |
| --- | --- | --- |
| Event type | ✓ Supported | ✓ Supported |
| Related resource ID | ✓ Supported | ✓ Supported |
| Event ID | ✓ Supported | ✓ Supported |
| Created timestamp | ✓ Supported | ✓ Supported |
| Reason | ✓ Supported | ✓ Supported |
| Changes | ❌ Unsupported | ✓ Supported |
| Data | ❌ Unsupported | ✓ Supported |

The following example shows how to retrieve the latest state of the related object directly from the event notification, without making an additional API call to fetch the event:

#### Java

```java
com.stripe.model.v2.core.EventNotification eventNotification = client.parseEventNotification(payload, signatureHeader, endpointSecret);
if (eventNotification instanceof V1BillingMeterErrorReportTriggeredEventNotification) {
  V1BillingMeterErrorReportTriggeredEventNotification notif =
      (V1BillingMeterErrorReportTriggeredEventNotification) eventNotification;
  // fetchRelatedObject() makes one network request to fetch the latest version
  // of the object associated with this event (a Meter, in this case).
  // No API call to retrieve the full Event is needed.
  Meter meter = notif.fetchRelatedObject();
}
```

The following example shows how to fetch the complete Event object when your integration requires additional data, such as the `data` hash with contextual information or the `changes` hash with previous attribute values:

#### Java

```java
com.stripe.model.v2.core.EventNotification eventNotification = client.parseEventNotification(payload, signatureHeader, endpointSecret);
if (eventNotification instanceof V1BillingMeterErrorReportTriggeredEventNotification) {
  V1BillingMeterErrorReportTriggeredEventNotification notif =
      (V1BillingMeterErrorReportTriggeredEventNotification) eventNotification;
  // Use fetchEvent() when you need additional data in the Event object,
  // such as the "data" hash with contextual info or the "changes" hash
  // with previous attribute values.
  com.stripe.model.v2.core.Event event = notif.fetchEvent();
  if (event instanceof V1BillingMeterErrorReportTriggeredEvent) {
    V1BillingMeterErrorReportTriggeredEvent typedEvent =
        (V1BillingMeterErrorReportTriggeredEvent) event;
    String summary = typedEvent.getData().getDeveloperMessageSummary();
  }
}
```

#### SDK typing

Thin events and their notifications are fully typed in the Stripe SDKs:

- **Event notification**: The initial lightweight payload is typed as `{EventType}EventNotification`.
- **Event**: After you retrieve the complete event using `fetchEvent()`, the object is typed as `{EventType}Event`.

### Snapshot events 

A snapshot event delivers a payload containing the complete `Event` object, including an eventually-consistent snapshot of the updated resource at the time the event was generated. Because this snapshot can be stale by the time your application processes it, fetch the latest version of the resource from the API before acting on the data.

Snapshot events are versioned by API version. The API version of a snapshot destination is fixed when the destination is created, and the payload structure is tied to that version. This requires you to manage version compatibility between your destination configuration and your integration code. For details on upgrading, see [Upgrade a snapshot event destination](https://docs.stripe.com/events/how-events-work.md#upgrade-versioning) or the [API versioning guide](https://docs.stripe.com/api/versioning.md).

Snapshot events are generated by both API v1 and API v2 resources. They include a `previous_attributes` property that indicates which fields changed, when applicable. See the [full list of snapshot events](https://docs.stripe.com/api/events/types.md).

Use snapshot events when thin events aren’t the right fit for your use case. Read more about [choosing a format](https://docs.stripe.com/events/how-events-work.md#choosing-event-format).

#### Payload structure 

The following example shows a `setup_intent.created` snapshot event, which includes the complete object definition as it was when the event was generated:

```json
{
  "id": "evt_1NG8Du2eZvKYlo2CUI79vXWy",
  "object": "event",
  "api_version": "2019-02-19",
  "created": 1686089970,
  "data": {
    "object": {
      "id": "seti_1NG8Du2eZvKYlo2C9XMqbR0x",
      "object": "setup_intent",
      "application": null,
      "automatic_payment_methods": null,
      "cancellation_reason": null,
      "client_secret": "seti_1NG8Du2eZvKYlo2C9XMqbR0x_secret_O2CdhLwGFh2Aej7bCY7qp8jlIuyR8DJ",
      "created": 1686089970,
      "customer": null,
      "description": null,
      "flow_directions": null,
      "last_setup_error": null,
      "latest_attempt": null,
      "livemode": false,
      "mandate": null,
      "metadata": {},
      "next_action": null,
      "on_behalf_of": null,
      "payment_method": "pm_1NG8Du2eZvKYlo2CYzzldNr7",
      "payment_method_options": {
        "acss_debit": {
          "currency": "cad",
          "mandate_options": {
            "interval_description": "First day of every month",
            "payment_schedule": "interval",
            "transaction_type": "personal"
          },
          "verification_method": "automatic"
        }
      },
      "payment_method_types": [
        "acss_debit"
      ],
      "single_use_mandate": null,
      "status": "requires_confirmation",
      "usage": "off_session"
    }
  },
  "livemode": false,
  "pending_webhooks": 0,
  "request": {
    "id": null,
    "idempotency_key": null
  },
  "type": "setup_intent.created"
}
```

#### Snapshot event versioning 

Snapshot event destinations have an API version set explicitly, or they use the default API version of the Stripe account. If you use a statically typed language SDK (.NET, Java, or Go) to process events, the API version configured on the destination must match the version used to generate the SDK. Mismatched versions cause deserialization failures.

Every API version before [2024-09-30.acacia](https://docs.stripe.com/changelog/acacia.md#2024-09-30.acacia) has breaking changes. Starting with the `2024-09-30.acacia` release, Stripe follows a [new API release process](https://stripe.com/blog/introducing-stripes-new-api-release-process) where monthly API versions within the same release contain no breaking changes. You can upgrade your event destination to any version within the same release without modifying your integration code. Twice a year, Stripe issues a new named release that begins with a breaking-change version.

Thin events are not subject to these constraints. Because thin event notification payloads are stable, you can upgrade your SDK or API version without updating your event destination configuration.

#### Upgrade an event destination to a new API version 

Use the [API upgrade guide](https://docs.stripe.com/upgrades.md) to update your event destinations and webhook endpoints.

## Event permissions

To view an event in the Dashboard, assign the [Administrator or Developer role](https://docs.stripe.com/get-started/account/teams/roles.md) to your user account. To retrieve an event using the API, use either a [secret API key](https://docs.stripe.com/keys.md#create-api-secret-key), which allows you to view all event types by default, or a [restricted API key](https://docs.stripe.com/keys.md#create-restricted-api-key) with `Read` access enabled for the specific event type’s resource. For example, you can grant `Read` access to `payment_intent` resources on your restricted API key to programmatically retrieve `payment_intent.succeeded events`.

## Event retention

In the **Events** tab in Workbench, you can access events within the last 13 months:

- Events less than 15 days old: you can view the full event payload, see delivery attempts, and manually resend these events.
- Events 16 to 30 days old: you can access the full event payload, but you can’t resend them or view delivery attempts.
- Events older than 30 days: you can only see a summary view with truncated fields. Resending and viewing delivery attempts aren’t available.

Use the [Retrieve event](https://docs.stripe.com/api/v2/core/events/retrieve.md) and [List events](https://docs.stripe.com/api/v2/core/events/list.md) APIs to access events with their full payload from the past 30 days.

## Recover from missing event types 

If your destination’s `enabled_events` list omits event types your integration needs, add those types and retrieve available historical events. Updating `enabled_events` affects future delivery only. Stripe doesn’t automatically deliver earlier events.

Before retrieving events, check the [event permissions](https://docs.stripe.com/events/how-events-work.md#event-permissions) and query the account context that produced them. For connected accounts, use the [Stripe-Account header](https://docs.stripe.com/connect/authentication.md#stripe-account-header). For organization accounts or other account relationships, use the [Stripe-Context header](https://docs.stripe.com/context.md). If your destination receives events from multiple accounts, query each affected account context separately.

1. Update `enabled_events` in the [Dashboard](https://dashboard.stripe.com/webhooks) or with the [event destinations API](https://docs.stripe.com/api/v2/event-destinations.md) to include the missing event types.

2. Retrieve and process historical events. Events that weren’t delivered to your destination might still be available through the Events API for 30 days. Use the API that matches your event format:

   - **Snapshot events**: Use [List events (v1)](https://docs.stripe.com/api/events/list.md) with the `type` parameter to filter by event type.
   - **Thin events**: Use [List events (v2)](https://docs.stripe.com/api/v2/core/events/list.md) with the `types` array parameter to filter by event type, then [retrieve the complete event](https://docs.stripe.com/api/v2/core/events/retrieve.md).

   Process the returned `Event` objects in your integration. If you need the latest resource state, retrieve the related resource separately.

3. Inspect older events in [Events](https://dashboard.stripe.com/workbench/events) in Workbench. Events older than 30 days have summaries with truncated fields, and you can’t retrieve their full payloads or resend them. Summaries are available for up to 13 months. See [Event retention](https://docs.stripe.com/events/how-events-work.md#event-retention) for details.

### Amazon EventBridge recovery constraints

For Amazon EventBridge destinations, you can’t manually resend events through Stripe. Follow the recovery steps in this section to retrieve available events from the past 30 days and process them directly in your application.

An EventBridge archive can’t recover events that Stripe never delivered to your event bus. For guidance on archiving events that the bus receives, see [EventBridge delivery behaviors](https://docs.stripe.com/event-destinations/eventbridge.md#event-delivery-behaviors).

## Event delivery behaviors 

This section describes how Stripe delivers events to your configured destinations.

### Automatic retries

Stripe attempts to deliver events to your destination for up to three days with an exponential back off in live mode. View when the next retry will occur, if applicable, in your event destination’s **Event deliveries** tab. We retry event deliveries created in a sandbox three times over the course of a few hours. If your destination has been disabled or deleted when we attempt a retry, we prevent future retries of that event. However, if you disable and then re-enable the event destination before we’re able to retry, you still see future retry attempts.

### Manual retries

You can manually retry events in the Stripe Dashboard by clicking **Resend** when looking at a specific event. This works for up to 15 days after the event creation.

### Event ordering

Stripe doesn’t guarantee the delivery of events in the order that they’re generated. For example, creating a subscription might generate the following events:

- `customer.subscription.created`
- `invoice.created`
- `invoice.paid`
- `charge.created` (if there’s a charge)

Make sure that your event destination isn’t dependent on receiving events in a specific order. Snapshot events record `created` in seconds, so distinct events can share a timestamp. Don’t use `created` to determine event order or whether you’ve already processed an event. Track [event IDs](https://docs.stripe.com/api/events/object.md#event_object-id) to identify duplicate deliveries instead. You can also use the API to retrieve any missing objects. For example, you can retrieve the invoice, charge, and subscription objects with the information from `invoice.paid` if you receive this event first.

### API versioning

The API version in your account settings when the event occurs dictates the API version, and therefore the structure of an [Event](https://docs.stripe.com/api/events.md) sent to your destination. For example, if your account is set to an older API version, such as 2015-02-16, and you change the API version for a specific request with [versioning](https://docs.stripe.com/api.md#versioning), the [Event](https://docs.stripe.com/api/events.md) object generated and sent to your destination is still based on the 2015-02-16 API version. You can’t change [Event](https://docs.stripe.com/api/events.md) objects after creation. For example, if you update a charge, the original charge event remains unchanged. As a result, subsequent updates to your account’s API version don’t retroactively alter existing [Event](https://docs.stripe.com/api/events.md) objects. Retrieving an older [Event](https://docs.stripe.com/api/events.md) by calling `/v1/events` using a newer API version also has no impact on the structure of the received event. You can set test event destinations to either your default API version or the latest API version. The [Event](https://docs.stripe.com/api/events.md) sent to the destination is structured for the event destination’s specified version.

## Verify webhook authenticity 

Always verify that webhook events originate from Stripe before acting on them. Use both of these protections:

- **Signature verification**: Stripe signs every webhook event by including a signature in the `Stripe-Signature` header. Verify this signature using the [official libraries](https://docs.stripe.com/events/set-up-events.md#signature-checking) to confirm the event wasn’t sent or modified by a third party.
- **IP allowlisting**: Stripe sends webhook events from a set list of [IP addresses](https://docs.stripe.com/ips.md). Configure your server or firewall to only accept webhook requests from these addresses.

For complete setup instructions, see [Set up events](https://docs.stripe.com/events/set-up-events.md).
