# Process incoming webhooks with event notification handlers

In each of our SDKs, we’ve created a specialized class that encapsulates the mechanics of parsing and validating a Stripe webhook. Event notification handlers take care of validating, parsing, and routing incoming thin event notifications to your business logic. They also provide authoring-time validation for your code, such as strong typing for all event data and typo protection for every event `type`.

To use this feature, you must write a function for each thin event type you want to handle. After you register these functions on the handler, the SDK calls them when you receive the corresponding event notification.

## Before you begin

You must use one of the following SDK versions (or higher) to use event notification handlers.

| Language | GA | Public Preview | Private Preview |
| --- | --- | --- | --- |
| Python | `v15.6.0` | `v14.2.0b1` | `v15.2.0a3` |
| Ruby | `v19.6.0` | `v18.2.0-beta.1` | `v19.2.0-alpha.3` |
| PHP | `v21.3.0` | `v19.2.0-beta.1` | `v20.2.0-alpha.4` |
| Go | `v86.4.0` | `v84.2.0-beta.1` | `v85.2.0-alpha.3` |
| Node | `v22.6.0` | `v20.2.0-beta.1` | `v22.2.0-alpha.4` |
| .NET | `v52.4.0` | `v50.2.0-beta.1` | `v51.2.0-alpha.3` |
| Java | `v33.4.0` | `v31.2.0-beta.1` | `v32.2.0-alpha.3` |

## Write a fallback callback

Write a function that runs whenever a dedicated callback hasn’t been registered for a specific event type. You call it with the `EventNotification`, a `StripeClient`, and additional information about the event.

This function could log the fact that you received an unexpected event or give you an error to alert you to the unexpected state. You can also add business logic in this function if you’re handling events that your SDK doesn’t have types for.

#### Python

```python
def fallback_callback(notif: EventNotification, client: StripeClient, details: UnhandledNotificationDetails):
    print(f'Got an unhandled event of type {notif.type}!')
```

As part of your migration, consider moving all of your [webhook endpoint](https://docs.stripe.com/webhooks.md) code into this function. Then, you can migrate individual event types to their own functions.

## Initialize your handler

In your [webhook endpoint](https://docs.stripe.com/webhooks.md), initialize an `EventNotificationHandler`, passing it your fallback callback. There’s a convenience method on `StripeClient` to simplify this step.

#### Webhook Endpoint

If you’re writing a traditional [webhook endpoint](https://docs.stripe.com/webhooks.md), you must pass your webhook secret to the handler constructor so the SDK can verify the authenticity of the webhook.

#### Python

```python
client = StripeClient(api_key)
handler = client.notification_handler(webhook_secret, fallback_callback)
```

#### Cloud Provider

If you’re using a cloud provider (such as [Amazon EventBridge](https://docs.stripe.com/event-destinations/eventbridge.md) or [Azure Event Grid](https://docs.stripe.com/event-destinations/eventgrid.md)), then there’s no verification needed. Use the `*WithoutVerification` version of the handler constructor to get the correct method signatures.

#### Python

```python
client = StripeClient(api_key)
handler = client.notification_handler_without_verification(fallback_callback)
```

## Write & register a callback

Next, write a function responsible for handling a specific event type. It uses the [event types](https://docs.stripe.com/webhooks.md?snapshot-or-thin=thin) released with the `Clover` API version in September 2025.

Your callback will receive the event notification cast to the correct class. You’ll also get a `StripeClient`, bound to the [context](https://docs.stripe.com/context.md) of the notification, which makes it easy to make additional API calls without juggling account ids.

#### Python

```python
# can be anywhere in your codebase
@handler.on_v1_billing_meter_error_report_triggered
def handle_meter_error(
    notif: V1BillingMeterErrorReportTriggeredEventNotification,
    client: StripeClient,
):
    event = notif.fetch_event()
    print(f"Err! No meter found: {event.data.developer_message_summary}")
```

You can register zero or more callbacks. If you don’t register any, all events will be routed to your fallback callback.

## Process events

Send incoming `POST` bodies into the handler. This replaces most of the original code in your webhook endpoint.

#### Webhook Endpoint

If you’re writing a traditional [webhook endpoint](https://docs.stripe.com/webhooks.md), you must pass the `Stripe-Signature` header to `.handle()` so the SDK can verify the authenticity of the webhook.

#### Python

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

    try:
        handler.handle(webhook_body, sig_header)
        return jsonify(success=True), 200
    except Exception as e:
        return jsonify(error=str(e)), 400
```

#### Cloud Provider

If you’re using a cloud provider (such as [Amazon EventBridge](https://docs.stripe.com/event-destinations/eventbridge.md) or [Azure Event Grid](https://docs.stripe.com/event-destinations/eventgrid.md)), then there’s no verification needed. Your handler’s `.handle()` method accepts only the webhook payload. It handles unwrapping the inner `Event` from the cloud provider’s envelope.

#### Python

```python
@app.route("/webhooks", methods=["POST"])
def webhook():
    try:
        handler.handle(request.data)
        return jsonify(success=True), 200
    except Exception as e:
        return jsonify(error=str(e)), 400
```

## How it works

A diagram explaining the flow of events through an event notification handler (See full diagram at https://docs.stripe.com/webhooks/event-notification-handlers)

```text
[.handle(event, …)] --> [preHandle registered?]
[preHandle registered?] -- yes --> [run preHandle]
[run preHandle] -- return false --> [Stop (without error)]
[run preHandle] -- return true --> [Known SDK event?]
[preHandle registered?] -- no --> [Known SDK event?]
[Known SDK event?] -- yes --> [Callback Registered?]
[Known SDK event?] -- no --> [Run fallbackCallback]
[Callback Registered?] -- yes --> [Run registered callback]
[Callback Registered?] -- no --> [Run fallbackCallback]
```

Internally, handling an event follows a few steps (shown in more detail above):

1. Parse and validate the incoming event.
2. Determine which callback you need to invoke.
3. Run that callback with the correctly typed `EventNotification` class.

Familiarize yourself with the following features.

### The `preHandle` method

Event notification handlers have a `preHandle` method designed around two main use cases:

1. Calling a function before invoking any callbacks (such as a logger).
2. Halting the processing of an event based on the content of that event. Use this for [deduplicating events](https://docs.stripe.com/webhooks.md#handle-duplicate-events)

You register the pre-handle callback in the same way as event-specific callbacks. And as with other callbacks, you invoke it with the parsed `EventNotification` class and a `StripeClient` instance bound to the event’s context

#### Python

```python
# example for testing; use something more durable for production
processed_event_ids = set()

@handler.pre_handle
def log_and_dedup(notif: EventNotification, client: StripeClient) -> bool:
    # log every incoming event
    print(f'Starting {notif.id}')

    # ignore duplicates...
    if notif.id in processed_event_ids:
        print(f"Already processed {notif.id}, skipping.")
        return False
    processed_event_ids.add(notif.id)

    # ... or event types you don't care about
    if notif.type == 'some.ignored.event':
        return False

    return True
```

The .NET SDK uses the `.Cancel` property of its native `event` object to control whether to continue event handling. Setting `e.Cancel = true;` stops execution (without error). Setting `e.Cancel = false;` (or not setting it at all) means an event-specific callback (or your fallback) are invoked as normal.

### Error handling

The event notification handler does no additional error handling or suppressing. Any SDK error (such as each language’s `SignatureVerificationError`) or error from your callbacks comes from the `.handle(...)` function.

## See also

- [Using thin events](https://docs.stripe.com/event-destinations.md#thin-events)
- [Receive Stripe events in your webhook endpoint](https://docs.stripe.com/webhooks.md)
- [Testing webhook endpoints](https://docs.stripe.com/automated-testing/webhooks.md)
