Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion examples/event_notification_handler_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,17 @@ def fallback_callback(
client = StripeClient(api_key)
handler = client.notification_handler(webhook_secret, fallback_callback)

# Handles events delivered through a channel that has already authenticated them, such as
# AWS EventBridge or Azure Event Grid. Those payloads carry no Stripe-Signature header.
unverified_handler = client.notification_handler_without_verification(
fallback_callback
)

# can be anywhere in your codebase

# can be anywhere in your codebase; registering on both handlers means either
# endpoint below will route this event type
@handler.on_v1_billing_meter_error_report_triggered
@unverified_handler.on_v1_billing_meter_error_report_triggered
def handle_meter_error(
notif: V1BillingMeterErrorReportTriggeredEventNotification,
client: StripeClient,
Expand All @@ -55,3 +63,13 @@ def webhook():
return jsonify(success=True), 200
except Exception as e:
return jsonify(error=str(e)), 500


@app.route("/webhook-from-cloud-provider", methods=["POST"])
def webhook_from_cloud_provider():
# no signature header to pass along; the channel already authenticated this event
try:
unverified_handler.handle(request.data)
return jsonify(success=True), 200
except Exception as e:
return jsonify(error=str(e)), 500
5 changes: 5 additions & 0 deletions stripe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ def add_beta_version(
from stripe._event import Event as Event
from stripe._event_notification_handler import (
StripeEventNotificationHandler as StripeEventNotificationHandler,
StripeEventNotificationHandlerWithoutVerification as StripeEventNotificationHandlerWithoutVerification,
UnhandledNotificationDetails as UnhandledNotificationDetails,
)
from stripe._event_service import EventService as EventService
Expand Down Expand Up @@ -874,6 +875,10 @@ def add_beta_version(
"stripe._event_notification_handler",
False,
),
"StripeEventNotificationHandlerWithoutVerification": (
"stripe._event_notification_handler",
False,
),
"UnhandledNotificationDetails": (
"stripe._event_notification_handler",
False,
Expand Down
76 changes: 65 additions & 11 deletions stripe/_event_notification_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1363,28 +1363,23 @@ class UnhandledNotificationDetails:
"""


class StripeEventNotificationHandler:
class _BaseEventNotificationHandler:
"""
Shared internal registration and dispatch machinery for the two user-facing event handlers.
"""

def __init__(
self,
client: "StripeClient",
webhook_secret: str,
fallback_callback: FallbackCallback,
) -> None:
self._registered_handlers = {}
self._client = client
self._webhook_secret = webhook_secret
self.fallback_callback = fallback_callback
# once this is true, adding additional handlers results in an error
self._has_handled_events = False

def handle(self, webhook_body: str, sig_header: str):
# isn't thread-safe, but we expect these to get registered synchronously at startup
self._has_handled_events = True

event_notif = self._client.parse_event_notification(
webhook_body, sig_header, self._webhook_secret
)

def _dispatch(self, event_notif: "EventNotification"):
# Create a new client with the event's context.
# This is thread-safe since we're not modifying the original client.
# The new client reuses the HTTP client to avoid TLS handshake overhead.
Expand Down Expand Up @@ -7165,3 +7160,62 @@ def on_v2_signals_account_signal_payment_delinquency_exposure_ready(
return func

# event-notification-registration-methods: The end of the section generated from our OpenAPI spec


class StripeEventNotificationHandler(_BaseEventNotificationHandler):
"""
An on-rails experience for handling Stripe event notifications. Define callbacks for individual event types and an instance of this class will be responsible for verifying and routing the event.
"""

def __init__(
self,
client: "StripeClient",
webhook_secret: str,
fallback_callback: FallbackCallback,
) -> None:
super().__init__(client, fallback_callback)
if not webhook_secret:
raise ValueError("webhook_secret must be a non-empty string")
self._webhook_secret = webhook_secret

def handle(self, webhook_body: str, sig_header: str):
# set before parsing, so that even a failed parse locks out registration.
# modification isn't thread-safe, but we expect callbacks to get registered synchronously at startup
# making a race condition here unlikely
self._has_handled_events = True

event_notif = self._client.parse_event_notification(
webhook_body, sig_header, self._webhook_secret
)

self._dispatch(event_notif)

@staticmethod
def without_verification(
client: "StripeClient",
fallback_callback: FallbackCallback,
) -> "StripeEventNotificationHandlerWithoutVerification":
return StripeEventNotificationHandlerWithoutVerification(
client, fallback_callback
)


class StripeEventNotificationHandlerWithoutVerification(
_BaseEventNotificationHandler
):
"""
A variant of StripeEventNotificationHandler that parses events without verifying webhook signatures. Intended for pre-authenticated channels like AWS EventBridge, Azure Event Grid, or your own pre-authenticated queuing system.

Prefer `StripeEventNotificationHandler.without_verification()` or `client.notification_handler_without_verification()` instead of constructing it directly.
"""

def handle(self, webhook_body: str):
self._has_handled_events = True

event_notif = (
self._client.parse_event_notification_without_verification(
webhook_body
)
)

self._dispatch(event_notif)
14 changes: 14 additions & 0 deletions stripe/_stripe_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from stripe._error import AuthenticationError
from stripe._event_notification_handler import (
StripeEventNotificationHandler,
StripeEventNotificationHandlerWithoutVerification,
FallbackCallback,
)
from stripe._request_options import extract_options_from_dict
Expand Down Expand Up @@ -391,6 +392,19 @@ def notification_handler(
self, webhook_secret, fallback_callback
)

def notification_handler_without_verification(
self, fallback_callback: FallbackCallback
) -> StripeEventNotificationHandlerWithoutVerification:
"""
A variant of StripeEventNotificationHandler that parses events without
verifying webhook signatures. Intended for pre-authenticated channels
like AWS EventBridge, Azure Event Grid, or your own queue system that
verifies payloads before storage.
"""
return StripeEventNotificationHandler.without_verification(
self, fallback_callback
)

# deprecated v1 services: The beginning of the section generated from our OpenAPI spec
@property
@deprecated(
Expand Down
Loading
Loading