# Registrieren Sie Stripe-Ereignisse in Ihrem Webhook-Endpoint Hören Sie auf Ihrem Webhook-Endpoint auf Ereignisse in Ihrem Stripe-Konto, damit Ihre Integration automatisch Reaktionen auslösen kann. > #### Ereignisse an Ihr AWS-Konto senden > > Sie können Ereignisse jetzt direkt an [Amazon EventBridge als Ereignisziel](https://docs.stripe.com/event-destinations/eventbridge.md) senden. Erstellen Sie ein Ereignisziel, um Ereignisse an einem HTTPS-Webhook-Endpoint zu empfangen. Nach der Registrierung eines Webhook-Endpoints kann Stripe Ereignisdaten in Echtzeit an den Webhook-Endpoint Ihrer Anwendung senden, wenn in Ihrem Stripe-Konto [Ereignisse](https://docs.stripe.com/event-destinations.md#events-overview) stattfinden. Stripe verwendet HTTPS, um Webhook-Ereignisse als JSON-Nutzlast, die ein [Ereignisobjekt](https://docs.stripe.com/api/events.md) enthält, an Ihre App zu senden. Der Empfang von Webhook-Ereignissen hilft Ihnen, auf asynchrone Ereignisse zu reagieren, z.B. wenn die Bank eines Kunden/einer Kundin eine Zahlung bestätigt, ein Kunde/eine Kundin eine Zahlungsanfechtung einleitet oder eine wiederkehrende Zahlung erfolgreich ist. ## Loslegen So empfangen Sie Webhook-Ereignisse in Ihrer App: 1. Erstellen Sie einen Webhook-Endpoint-Handler, um POST-Anfragen für Ereignisdaten zu empfangen. 1. Testen Sie Ihren Webhook-Endpoint-Handler lokal mit der Stripe CLI. 1. Erstellen Sie ein neues [Ereignisziel](https://docs.stripe.com/event-destinations.md) für Ihren Webhook-Endpoint. 1. Sichern Sie Ihren Webhook-Endpoint. Sie können einen Endpoint registrieren und erstellen, um mehrere verschiedene Ereignistypen auf einmal zu verarbeiten, oder individuelle Endpoints für bestimmte Ereignisse einrichten. ## Nicht unterstütztes Ereignistypverhalten für Organisationsereignisziele Stripe sendet die meisten Ereignistypen asynchron, wartet aber bei einigen Ereignistypen auf eine Antwort. In diesen Fällen verhält sich Stripe unterschiedlich, je nachdem, ob das Ereignisziel antwortet oder nicht. Wenn Ihr Ereignisziel [Organisations-](https://docs.stripe.com/get-started/account/orgs.md)Ereignisse empfängt, besitzen diejenigen, die eine Antwort erfordern, die folgenden Einschränkungen: - Sie können `issuing_authorization.request` für Organisationsziele nicht abonnieren. Richten Sie stattdessen einen [Webhook-Endpoint](https://docs.stripe.com/webhooks.md#example-endpoint) in einem Stripe-Konto innerhalb der Organisation ein, um diesen Ereignistyp zu abonnieren. Verwenden Sie `issuing_authorization.request`, um Kaufanfragen in Echtzeit zu autorisieren. - Organisationsziele, die `checkout_sessions.completed` empfangen, können nicht [mit dem Weiterleitungsverhalten](https://docs.stripe.com/checkout/fulfillment.md#redirect-hosted-checkout) umgehen, wenn Sie [Checkout](https://docs.stripe.com/payments/checkout.md) direkt in Ihre Website einbetten oder Kunden/Kundinnen auf eine von Stripe gehostete Bezahlseite umleiten. Um das Weiterleitungsverhalten von Checkout zu beeinflussen, verarbeiten Sie diesen Ereignistyp mit einem [Webhook Endpoint](https://docs.stripe.com/webhooks.md#example-endpoint), der in einem Stripe-Konto innerhalb der Organisation konfiguriert ist. - Organisationsziele, die erfolglos auf ein `invoice.created`-Ereignis reagieren, können den [automatischen Rechnungsabschluss nicht beeinflussen, wenn Sie den automatischen Einzug](https://docs.stripe.com/billing/subscriptions/webhooks.md#understand) verwenden. Sie müssen diesen Ereignistyp mit einem [Webhook-Endpoint](https://docs.stripe.com/webhooks.md#example-endpoint) verarbeiten, der in einem Stripe-Konto innerhalb der Organisation konfiguriert ist, um den automatischen Abschluss der Rechnung auszulösen. ## Handler erstellen Ermitteln Sie in der Stripe-API-Dokumentation die [Thin-Ereignisobjekte](https://docs.stripe.com/api/v2/events/event-types.md) oder [Snapshot-Ereignisobjekte](https://docs.stripe.com/api/events/object.md), die Ihr Webhook-Handler verarbeiten muss. Richten Sie eine HTTP- oder HTTPS-Endpoint-Funktion ein, die Webhook-Anfragen mit einer POST-Methode akzeptieren kann. Wenn Sie noch dabei sind, Ihre Endpoint-Funktion auf Ihrem lokalen Computer zu entwickeln, kann HTTP für diese verwendet werden. Nachdem sie öffentlich zugänglich ist, muss Ihre Webhook-Endpoint-Funktion HTTPS nutzen. Richten Sie Ihre Endpoint-Funktion wie folgt ein: - POST-Anfragen mit einer JSON-Nutzlast verarbeitet, die aus einem [Ereignisobjekt](https://docs.stripe.com/api/events/object.md) besteht. - Für [Organisations-](https://docs.stripe.com/get-started/account/orgs.md)Ereignis-Handler wird der Wert `context` untersucht, um festzustellen, welches Konto in einem Unternehmen das Ereignis erzeugt hat, und dann der Header `Stripe-Context` entsprechend dem Wert `context` festgelegt. - Gibt schnell einen erfolgreichen Statuscode (`2xx`) zurück, bevor eine komplexe Logik angewendet wird, die eine Zeitüberschreitung verursachen könnte. Beispielsweise müssen Sie eine `200`-Antwort zurückgeben, bevor Sie eine Kundenrechnung in Ihrem Buchhaltungssystem als bezahlt aktualisieren können. > Verwenden Sie unseren [interaktiven Webhook-Endpoint-Builder](https://docs.stripe.com/webhooks/quickstart.md), um eine Webhook-Endpoint-Funktion in Ihrer Programmiersprache zu erstellen. #### Beispiel-Endpoint Bei diesem Codeausschnitt handelt es sich um eine Webhook-Funktion, die so konfiguriert ist, dass sie nach empfangenen Ereignissen sucht, das Ereignis verarbeitet und eine `200`-Antwort zurückgibt. Verweisen Sie auf den [Snapshot](https://docs.stripe.com/event-destinations.md#events-overview)-Ereignis-Handler, wenn Sie API v1-Ressourcen verwenden, und verweisen Sie auf den [Thin](https://docs.stripe.com/event-destinations.md#events-overview)-Ereignis-Handler, wenn Sie API v2-Ressourcen verwenden. #### Snapshot-Ereignis-Handler Wenn Sie einen Snapshot-Ereignis-Handler erstellen, verwenden Sie die API-Objektdefinition zum Zeitpunkt des Ereignisses für Ihre Logik, indem Sie auf die `data.object`-Felder des Ereignisses zugreifen. Sie können die API-Ressource auch von der Stripe API abrufen, um auf die neueste und aktuellste Objektdefinition zuzugreifen. #### Ruby ```ruby require 'json' # 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 ``` #### Python ```python import json from django.http import HttpResponse # Using Django # 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_...' @csrf_exempt def my_webhook_view(request): payload = request.body event = None try: event = stripe.Event.construct_from( json.loads(payload), stripe.api_key ) except ValueError as e: # Invalid payload return HttpResponse(status=400) if endpoint_secret: # Only verify the event if you've defined an endpoint secret # Otherwise, use the basic event deserialized with JSON sig_header = request.headers.get('stripe-signature') try: event = stripe.Webhook.construct_event( payload, sig_header, endpoint_secret ) except stripe.error.SignatureVerificationError as e: print('⚠️ Webhook signature verification failed.' + str(e)) return jsonify(success=False) # Handle the event if event.type == '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) elif event.type == '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: print('Unhandled event type {}'.format(event.type)) return HttpResponse(status=200) ``` #### PHP ```php // 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_...'; $payload = @file_get_contents('php://input'); $event = null; try { $event = \Stripe\Event::constructFrom( json_decode($payload, true) ); } catch(\UnexpectedValueException $e) { // Invalid payload http_response_code(400); exit(); } if ($endpoint_secret) { // Only verify the event if you've defined an endpoint secret // Otherwise, use the basic decoded event $sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE']; try { $event = \Stripe\Webhook::constructEvent( $payload, $sig_header, $endpoint_secret ); } catch(\Stripe\Exception\SignatureVerificationException $e) { // Invalid signature echo '⚠️ Webhook error while validating signature.'; http_response_code(400); exit(); } } // Handle the event switch ($event->type) { case 'payment_intent.succeeded': $paymentIntent = $event->data->object; // contains a \Stripe\PaymentIntent // Then define and call a method to handle the successful payment intent. // handlePaymentIntentSucceeded($paymentIntent); break; case 'payment_method.attached': $paymentMethod = $event->data->object; // contains a \Stripe\PaymentMethod // Then define and call a method to handle the successful attachment of a PaymentMethod. // handlePaymentMethodAttached($paymentMethod); break; // ... handle other event types default: echo 'Received unknown event type ' . $event->type; } http_response_code(200); ``` #### Java ```java // Using the Spark framework (http://sparkjava.com) public Object handle(Request request, Response response) { // 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 // I# If you defined your endpoint using the API or the Dashboard, check your webhook settings for your endpoint secret: https://dashboard.stripe.com/webhooks String endpointSecret = "whsec_..."; String payload = request.body(); Event event = null; try { event = ApiResource.GSON.fromJson(payload, Event.class); } catch (JsonSyntaxException e) { // Invalid payload response.status(400); return ""; } String sigHeader = request.headers("Stripe-Signature"); if(endpointSecret != null && sigHeader != null) { // Only verify the event if you’ve defined an endpoint secret // Otherwise, use the basic event deserialized with GSON try { event = Webhook.constructEvent( payload, sigHeader, endpointSecret ); } catch (SignatureVerificationException e) { // Invalid signature System.out.println("⚠️ Webhook error while validating signature."); response.status(400); return ""; } } // Deserialize the nested object inside the event EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer(); StripeObject stripeObject = null; if (dataObjectDeserializer.getObject().isPresent()) { stripeObject = dataObjectDeserializer.getObject().get(); } else { // Deserialization failed, probably due to an API version mismatch. // Refer to the Javadoc documentation on `EventDataObjectDeserializer` for // instructions on how to handle this case, or return an error here. } // Handle the event switch (event.getType()) { case "payment_intent.succeeded": PaymentIntent paymentIntent = (PaymentIntent) stripeObject; // Then define and call a method to handle the successful payment intent. // handlePaymentIntentSucceeded(paymentIntent); break; case "payment_method.attached": PaymentMethod paymentMethod = (PaymentMethod) stripeObject; // Then define and call a method to handle the successful attachment of a PaymentMethod. // handlePaymentMethodAttached(paymentMethod); break; // ... handle other event types default: System.out.println("Unhandled event type: " + event.getType()); } response.status(200); return ""; } ``` #### Node.js ```javascript const express = require('express'); const app = express(); // 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 const endpointSecret = 'whsec_...'; // The express.raw middleware keeps the request body unparsed; // this is necessary for the signature verification process app.post('/webhook', express.raw({type: 'application/json'}), (request, response) => { let event; if (endpointSecret) { // Get the signature sent by Stripe const signature = request.headers['stripe-signature']; try { event = stripe.webhooks.constructEvent( request.body, signature, endpointSecret ); } catch (err) { console.log(`⚠️ Webhook signature verification failed.`, err.message); return response.sendStatus(400); } // Handle the event switch (event.type) { case 'payment_intent.succeeded': const paymentIntent = event.data.object; // Then define and call a method to handle the successful payment intent. // handlePaymentIntentSucceeded(paymentIntent); break; case 'payment_method.attached': const paymentMethod = event.data.object; // Then define and call a method to handle the successful attachment of a PaymentMethod. // handlePaymentMethodAttached(paymentMethod); break; // ... handle other event types default: console.log(`Unhandled event type ${event.type}`); } // Return a response to acknowledge receipt of the event response.json({received: true}); }); app.listen(4242, () => console.log('Running on port 4242')); ``` #### Go ```go http.HandleFunc("/webhook", func(w http.ResponseWriter, req *http.Request) { const MaxBodyBytes = int64(65536) req.Body = http.MaxBytesReader(w, req.Body, MaxBodyBytes) payload, err := ioutil.ReadAll(req.Body) if err != nil { fmt.Fprintf(os.Stderr, "Error reading request body: %v\n", err) w.WriteHeader(http.StatusServiceUnavailable) return } event := stripe.Event{} if err := json.Unmarshal(payload, &event); err != nil { fmt.Fprintf(os.Stderr, "Failed to parse webhook body json: %v\n", err.Error()) w.WriteHeader(http.StatusBadRequest) return } // Unmarshal the event data into an appropriate struct depending on its Type switch event.Type { case "payment_intent.succeeded": var paymentIntent stripe.PaymentIntent err := json.Unmarshal(event.Data.Raw, &paymentIntent) if err != nil { fmt.Fprintf(os.Stderr, "Error parsing webhook JSON: %v\n", err) w.WriteHeader(http.StatusBadRequest) return } // Then define and call a func to handle the successful payment intent. // handlePaymentIntentSucceeded(paymentIntent) case "payment_method.attached": var paymentMethod stripe.PaymentMethod err := json.Unmarshal(event.Data.Raw, &paymentMethod) if err != nil { fmt.Fprintf(os.Stderr, "Error parsing webhook JSON: %v\n", err) w.WriteHeader(http.StatusBadRequest) return } // Then define and call a func to handle the successful attachment of a PaymentMethod. // handlePaymentMethodAttached(paymentMethod) // ... handle other event types default: fmt.Fprintf(os.Stderr, "Unhandled event type: %s\n", event.Type) } w.WriteHeader(http.StatusOK) }) ``` #### .NET ```csharp using System; using System.IO; using Microsoft.AspNetCore.Mvc; using Stripe; namespace workspace.Controllers { [Route("api/[controller]")] public class StripeWebHook : Controller { [HttpPost] public async Task Index() { var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); const string endpointSecret = "whsec_..."; try { var stripeEvent = EventUtility.ParseEvent(json); var signatureHeader = Request.Headers["Stripe-Signature"]; stripeEvent = EventUtility.ConstructEvent(json,signatureHeader, endpointSecret); // Handle the event // If on SDK version < 46, use class Events instead of EventTypes if (stripeEvent.Type == EventTypes.PaymentIntentSucceeded) { var paymentIntent = stripeEvent.Data.Object as PaymentIntent; // Then define and call a method to handle the successful payment intent. // handlePaymentIntentSucceeded(paymentIntent); } else if (stripeEvent.Type == EventTypes.PaymentMethodAttached) { var paymentMethod = stripeEvent.Data.Object as PaymentMethod; // Then define and call a method to handle the successful attachment of a PaymentMethod. // handlePaymentMethodAttached(paymentMethod); } // ... handle other event types else { // Unexpected event type Console.WriteLine("Unhandled event type: {0}", stripeEvent.Type); } return Ok(); } catch (StripeException e) { return BadRequest(); } } } } ``` #### Thin-Ereignishandler (Clover+) Wenn Sie einen Thin-Eventhandler erstellen, verwenden Sie die `fetchRelatedObject()`-Methode, um die neueste Version des mit dem Ereignis verbundenen Objekts abzurufen. Ereignisse können [zusätzliche Daten](https://docs.stripe.com/event-destinations.md#fetch-data) enthalten, die Sie nur über die Instanzmethode `.fetchEvent()` in `EventNotification` abrufen können. Die genaue Form dieser Daten hängt vom `type` des Ereignisses ab. Ereignistypen müssen zum Zeitpunkt der Veröffentlichung verfügbar sein, um Klassen in dieser SDK-Version zu generieren. Um Ereignisse zu verarbeiten, für die das SDK keine Klassen hat, verwenden Sie die `UnknownEventNotification`-Klasse. #### 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 ``` #### Ruby ```ruby require "stripe" require "sinatra" api_key = ENV.fetch("STRIPE_API_KEY", nil) # Retrieve the webhook secret from the environment variable webhook_secret = ENV.fetch("WEBHOOK_SECRET", nil) client = Stripe::StripeClient.new(api_key) post "/webhook" do webhook_body = request.body.read sig_header = request.env["HTTP_STRIPE_SIGNATURE"] event_notification = client.parse_event_notification(webhook_body, sig_header, webhook_secret) if event_notification.instance_of?(Stripe::Events::V1BillingMeterErrorReportTriggeredEventNotification) # there's basic info about the related object in the notification puts "Received event for meter id:", event_notification.related_object.id # or you can fetch the full object form the API for more details meter = event_notification.fetch_related_object puts "Meter #{meter.display_name} (#{meter.id}) had a problem" # And you can always fetch the full event: event = event_notification.fetch_event puts "More info:", event.data.developer_message_summary elsif event_notification.instance_of?(Stripe::Events::UnknownEventNotification) # 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. if event_notification.type == "some.new.event" # your logic goes here end end # Record the failures and alert your team status 200 end ``` #### PHP ```php post('/webhook', static function ($request, $response) use ($client, $webhook_secret) { $webhook_body = $request->getBody()->getContents(); $sig_header = $request->getHeaderLine('Stripe-Signature'); try { $event_notification = $client->parseEventNotification($webhook_body, $sig_header, $webhook_secret); // check what type of event notification we have if ($event_notification instanceof Stripe\Events\V1BillingMeterErrorReportTriggeredEventNotification) { // there's basic info about the related object in the notification echo "Meter with id {$event_notification->related_object->id} reported an error\n"; // or you can fetch the full object form the API for more details $meter = $event_notification->fetchRelatedObject(); echo "Meter {$meter->display_name} ({$meter->id}) had a problem\n"; # And you can always fetch the full event: $event = $event_notification->fetchEvent(); echo "More info: {$event->data->developer_message_summary}\n"; } else if ($event_notification instanceof Stripe\Events\UnknownEventNotification) { // 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. if ($event_notification->type === 'some.new.event') { // handle it the same way as above } } return $response->withStatus(200); } catch (Exception $e) { return $response->withStatus(400)->withJson(['error' => $e->getMessage()]); } }); $app->run(); ``` #### Java ```java import com.stripe.StripeClient; import com.stripe.events.UnknownEventNotification; import com.stripe.events.V1BillingMeterErrorReportTriggeredEvent; import com.stripe.events.V1BillingMeterErrorReportTriggeredEventNotification; import com.stripe.exception.StripeException; import com.stripe.model.billing.Meter; import com.stripe.model.v2.core.EventNotification; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; public class EventNotificationWebhookHandler { private static final String API_KEY = System.getenv("STRIPE_API_KEY"); private static final String WEBHOOK_SECRET = System.getenv("WEBHOOK_SECRET"); private static final StripeClient client = new StripeClient(API_KEY); public static void main(String[] args) throws IOException { HttpServer server = HttpServer.create(new InetSocketAddress(4242), 0); server.createContext("/webhook", new WebhookHandler()); server.setExecutor(null); server.start(); } static class WebhookHandler implements HttpHandler { @Override public void handle(HttpExchange exchange) throws IOException { if ("POST".equals(exchange.getRequestMethod())) { InputStream requestBody = exchange.getRequestBody(); String webhookBody = new String(requestBody.readAllBytes(), StandardCharsets.UTF_8); String sigHeader = exchange.getRequestHeaders().getFirst("Stripe-Signature"); try { EventNotification notif = client.parseEventNotification(webhookBody, sigHeader, WEBHOOK_SECRET); if (notif instanceof V1BillingMeterErrorReportTriggeredEventNotification) { V1BillingMeterErrorReportTriggeredEventNotification eventNotification = (V1BillingMeterErrorReportTriggeredEventNotification) notif; // there's basic info about the related object in the notification System.out.println( "Meter w/ id " + eventNotification.getRelatedObject().getId() + " had a problem"); // or you can fetch the full object form the API for more details Meter meter = eventNotification.fetchRelatedObject(); StringBuilder sb = new StringBuilder(); sb.append("Meter ") .append(meter.getDisplayName()) .append(" (") .append(meter.getId()) .append(") had a problem"); System.out.println(sb.toString()); // And you can always fetch the full event: V1BillingMeterErrorReportTriggeredEvent event = eventNotification.fetchEvent(); System.out.println("More info: " + event.getData().getDeveloperMessageSummary()); } else if (notif instanceof UnknownEventNotification) { // 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. UnknownEventNotification unknownEvent = (UnknownEventNotification) notif; if (unknownEvent.getType().equals("some.new.event")) { // you can still `.fetchEvent()` and `.fetchRelatedObject()`, but the latter may // return `null` if that event type doesn't have a related object. } } exchange.sendResponseHeaders(200, -1); } catch (StripeException e) { exchange.sendResponseHeaders(400, -1); } } else { exchange.sendResponseHeaders(405, -1); } exchange.close(); } } } ``` #### Typescript ```typescript import {Stripe} from 'stripe'; import express from 'express'; const app = express(); const apiKey = process.env.STRIPE_API_KEY ?? ''; const webhookSecret = process.env.WEBHOOK_SECRET ?? ''; const client = new Stripe(apiKey); app.post( '/webhook', express.raw({type: 'application/json'}), async (req, res) => { const sig = req.headers['stripe-signature'] ?? ''; try { const eventNotification = client.parseEventNotification( req.body, sig, webhookSecret ); // TS will narrow event based on the `type` property if (eventNotification.type == 'v1.billing.meter.error_report_triggered') { // this this block, eventNotification is correctly // a Stripe.Events.V1BillingMeterErrorReportTriggeredEventNotification // there's basic info about the related object in the notification console.log( `Meter w/ id ${eventNotification.related_object.id} had a problem` ); // or you can fetch the full object from the API for more details const meter = await eventNotification.fetchRelatedObject(); console.log(`Meter ${meter.display_name} (${meter.id}) had a problem`); // And you can always fetch the full event: const event = await eventNotification.fetchEvent(); console.log(`More info: ${event.data.developer_message_summary}`); } else if (eventNotification.type === 'v1.billing.meter.no_meter_found') { // in this block, eventNotification is correctly // a Stripe.Events.V1BillingMeterNoMeterFoundEventNotification // that interface doesn't define `fetchRelatedObject()` because the event // has no related object. so this line would correctly give a type error: // eventNotification.fetchRelatedObject(); // but fetching the event always works: const event = await eventNotification.fetchEvent(); console.log( `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. // In that case, you ignore the type mismatch and cast to UnknownEventNotification // @ts-expect-error } else if (eventNotification.type === 'some.new.event') { const unknownEvent = eventNotification as Stripe.Events.UnknownEventNotification; // you can still fetch the related object, if one exists // but its type is `unknown` const obj = await unknownEvent.fetchRelatedObject(); // and you can still fetch the event: const event = await unknownEvent.fetchEvent(); // @ts-expect-error console.log(`Got new event: ${event.data}`); } res.sendStatus(200); } catch (err) { console.log(`Webhook Error: ${(err as any).stack}`); res.status(400).send(`Webhook Error: ${(err as any).message}`); } } ); app.listen(4242, () => console.log('Running on port 4242')); ``` #### Go ```go package main import ( "context" "io" "log/slog" "net/http" "os" "github.com/stripe/stripe-go/v83" ) func main() { http.HandleFunc("/webhook", func(w http.ResponseWriter, req *http.Request) { const MaxBodyBytes = int64(65536) req.Body = http.MaxBytesReader(w, req.Body, MaxBodyBytes) payload, err := io.ReadAll(req.Body) if err != nil { fmt.Fprintf(os.Stderr, "Error reading request body: %v\n", err) w.WriteHeader(http.StatusInternalServerError) return } eventNotification, err := client.ParseEventNotification(payload, req.Header.Get("Stripe-Signature"), webhookSecret) if err != nil { fmt.Fprintf(os.Stderr, "Error reading request body: %v\n", err) w.WriteHeader(http.StatusInternalServerError) return } // Unmarshal the event data into an appropriate struct depending on its Type switch evt := eventNotification.(type) { case *stripe.V1BillingMeterErrorReportTriggeredEventNotification: // there's basic info about the related object in the notification fmt.Printf("Meter w/ id %s had a problem\n", evt.RelatedObject.ID) // or you can fetch the full object form the API for more details meter, err := evt.FetchRelatedObject(context.TODO()) if err != nil { fmt.Fprintf(os.Stderr, "Error fetching related object: %v\n", err) w.WriteHeader(http.StatusInternalServerError) return } sb := fmt.Sprintf("Meter %s (%s) had a problem", meter.DisplayName, meter.ID) fmt.Println(sb) // And you can always fetch the full event: event, err := evt.FetchEvent(context.TODO()) if err != nil { fmt.Fprintf(os.Stderr, "Error fetching event: %v\n", err) w.WriteHeader(http.StatusInternalServerError) return } fmt.Printf("More info: %s\n", event.Data.DeveloperMessageSummary) case *stripe.UnknownEventNotification: // 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. switch evt.Type { case "some.new.event": // you can still `.FetchEvent()` and `.FetchRelatedObject()`, but the latter may // return `nil` if that event type doesn't have a related object. return } default: fmt.Fprintf(os.Stderr, "Purposefully skipping the handling of event w/ type: %s\n", evt.GetEventNotification().Type) } w.WriteHeader(http.StatusOK) }) err := http.ListenAndServe(":4242", nil) if err != nil { fmt.Println(err) os.Exit(1) } } ``` #### .NET ```csharp using System; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Stripe; using Stripe.Events; [Route("api/[controller]")] [ApiController] public class EventNotificationWebhookHandler : ControllerBase { private readonly StripeClient client; private readonly string webhookSecret; public EventNotificationWebhookHandler() { var apiKey = Environment.GetEnvironmentVariable("STRIPE_API_KEY"); client = new StripeClient(apiKey); webhookSecret = Environment.GetEnvironmentVariable("WEBHOOK_SECRET") ?? string.Empty; } [HttpPost] public async Task Index() { var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); try { var eventNotification = client.ParseEventNotification(json, Request.Headers["Stripe-Signature"], webhookSecret); // match on the type of the class to determine what event you have if (eventNotification is V1BillingMeterErrorReportTriggeredEventNotification notif) { // there's basic info about the related object in the notification Console.WriteLine( $"Meter w/ id {notif.RelatedObject.Id} had a problem"); // or you can fetch the full object form the API for more details var meter = await notif.FetchRelatedObjectAsync(); Console.WriteLine($"Meter {meter.DisplayName} ({meter.Id}) had a problem"); // And you can always fetch the full event: var evt = await notif.FetchEventAsync(); Console.WriteLine($"More info: {evt.Data.DeveloperMessageSummary}"); } else if (eventNotification is UnknownEventNotification unknownEvt) { // 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. if (unknownEvt.Type == "some.other.event") { // you can still `.fetchEvent()` and `.fetchRelatedObject()`, but the latter may // return `null` if that event type doesn't have a related object. } } return Ok(); } catch (StripeException e) { return BadRequest(e.Message); } } } ``` #### Thin-Ereignishandler (Acacia oder Basil) Wenn Sie einen Thin-Ereignis-Handler erstellen, verwenden Sie die Methode `fetchRelatedObject()`, um die neueste Version des mit dem Ereignis verknüpften Objekts abzurufen. Thin-Ereignisse können [zusätzliche Kontextdaten](https://docs.stripe.com/event-destinations.md#fetch-data) enthalten, die Sie nur mit der API abrufen können. Verwenden Sie den Aufruf `retrieve()` mit der Thin-Ereignis-ID, um auf diese zusätzlichen Nutzlastfelder zuzugreifen. #### Python ```python import os from stripe import StripeClient from stripe.events import V1BillingMeterErrorReportTriggeredEvent 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: thin_event = client.parse_thin_event(webhook_body, sig_header, webhook_secret) # Fetch the event data to understand the failure event = client.v2.core.events.retrieve(thin_event.id) if isinstance(event, V1BillingMeterErrorReportTriggeredEvent): meter = event.fetch_related_object() meter_id = meter.id # Record the failures and alert your team # Add your logic here return jsonify(success=True), 200 except Exception as e: return jsonify(error=str(e)), 400 if __name__ == '__main__': app.run(port=4242) ``` #### Ruby ```ruby require "stripe" require "sinatra" api_key = ENV.fetch("STRIPE_API_KEY", nil) # Retrieve the webhook secret from the environment variable webhook_secret = ENV.fetch("WEBHOOK_SECRET", nil) client = Stripe::StripeClient.new(api_key) post "/webhook" do webhook_body = request.body.read sig_header = request.env["HTTP_STRIPE_SIGNATURE"] thin_event = client.parse_thin_event(webhook_body, sig_header, webhook_secret) # Fetch the event data to understand the failure event = client.v2.core.events.retrieve(thin_event.id) if event.instance_of? Stripe::V1BillingMeterErrorReportTriggeredEvent meter = event.fetch_related_object meter_id = meter.id end # Record the failures and alert your team # Add your logic here status 200 end ``` #### PHP ```php post('/webhook', function ($request, $response) use ($client, $webhook_secret) { $webhook_body = $request->getBody()->getContents(); $sig_header = $request->getHeaderLine('Stripe-Signature'); try { $thin_event = $client->parseThinEvent($webhook_body, $sig_header, $webhook_secret); // Fetch the event data to understand the failure $event = $client->v2->core->events->retrieve($thin_event->id); if ($event instanceof \Stripe\Events\V1BillingMeterErrorReportTriggeredEvent) { $meter = $event->fetchRelatedObject(); $meter_id = $meter->id; // Record the failures and alert your team // Add your logic here } return $response->withStatus(200); } catch (\Exception $e) { return $response->withStatus(400)->withJson(['error' => $e->getMessage()]); } }); $app->run(); ``` #### Java ```java import com.stripe.StripeClient; import com.stripe.events.V1BillingMeterErrorReportTriggeredEvent; import com.stripe.exception.StripeException; import com.stripe.model.ThinEvent; import com.stripe.model.billing.Meter; import com.stripe.model.v2.Event; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import java.io.IOException; import java.io.InputStream; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; public class StripeWebhookHandler { private static final String API_KEY = System.getenv("STRIPE_API_KEY"); private static final String WEBHOOK_SECRET = System.getenv("WEBHOOK_SECRET"); private static final StripeClient client = new StripeClient(API_KEY); public static void main(String[] args) throws IOException { HttpServer server = HttpServer.create(new InetSocketAddress(4242), 0); server.createContext("/webhook", new WebhookHandler()); server.setExecutor(null); server.start(); } static class WebhookHandler implements HttpHandler { @Override public void handle(HttpExchange exchange) throws IOException { if ("POST".equals(exchange.getRequestMethod())) { InputStream requestBody = exchange.getRequestBody(); String webhookBody = new String(requestBody.readAllBytes(), StandardCharsets.UTF_8); String sigHeader = exchange.getRequestHeaders().getFirst("Stripe-Signature"); try { ThinEvent thinEvent = client.parseThinEvent(webhookBody, sigHeader, WEBHOOK_SECRET); // Fetch the event data to understand the failure Event baseEvent = client.v2().core().events().retrieve(thinEvent.getId()); if (baseEvent instanceof V1BillingMeterErrorReportTriggeredEvent) { V1BillingMeterErrorReportTriggeredEvent event = (V1BillingMeterErrorReportTriggeredEvent) baseEvent; Meter meter = event.fetchRelatedObject(); String meterId = meter.getId(); // Record the failures and alert your team // Add your logic here } exchange.sendResponseHeaders(200, -1); } catch (StripeException e) { exchange.sendResponseHeaders(400, -1); } } else { exchange.sendResponseHeaders(405, -1); } exchange.close(); } } } ``` #### Node.js ```javascript const express = require('express'); const {Stripe} = require('stripe'); const app = express(); const apiKey = process.env.STRIPE_API_KEY; const webhookSecret = process.env.WEBHOOK_SECRET; const client = new Stripe(apiKey); app.post( '/webhook', express.raw({type: 'application/json'}), async (req, res) => { const sig = req.headers['stripe-signature']; try { const thinEvent = client.parseThinEvent(req.body, sig, webhookSecret); // Fetch the event data to understand the failure const event = await client.v2.core.events.retrieve(thinEvent.id); if (event.type == 'v1.billing.meter.error_report_triggered') { const meter = await event.fetchRelatedObject(); const meterId = meter.id; // Record the failures and alert your team // Add your logic here } res.sendStatus(200); } catch (err) { console.log(`Webhook Error: ${err.message}`); res.status(400).send(`Webhook Error: ${err.message}`); } }, ); app.listen(4242, () => console.log('Running on port 4242')); ``` #### Go ```go package main import ( "context" "io" "log/slog" "net/http" "os" "github.com/stripe/stripe-go/v82" ) func main() { apiKey := os.Getenv("STRIPE_API_KEY") webhookSecret := os.Getenv("STRIPE_WEBHOOK_SECRET") client := stripe.NewClient(apiKey) http.HandleFunc("/webhook", func(w http.ResponseWriter, req *http.Request) { defer req.Body.Close() payload, err := io.ReadAll(req.Body) if err != nil { slog.Error("Reading request body", "error", err) w.WriteHeader(http.StatusInternalServerError) return } thinEvent, err := client.ParseThinEvent(payload, req.Header.Get("Stripe-Signature"), webhookSecret) if err != nil { slog.Error("Parsing thin event", "error", err) w.WriteHeader(http.StatusInternalServerError) return } event, err := client.V2CoreEvents.Retrieve(context.TODO(), thinEvent.ID, nil) if err != nil { slog.Error("Retrieving snapshot event", "error", err) w.WriteHeader(http.StatusInternalServerError) return } switch e := event.(type) { case *stripe.V1BillingMeterErrorReportTriggeredEvent: meter, err := e.FetchRelatedObject() if err != nil { slog.Error("Error fetching related object", "error", err) w.WriteHeader(http.StatusInternalServerError) return } meterID := meter.ID // Add your logic here } w.WriteHeader(http.StatusOK) }) err := http.ListenAndServe(":4242", nil) if err != nil { slog.Error("Starting server", "error", err) os.Exit(1) } } ``` #### .NET ```csharp using System; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Stripe; using Stripe.Events; [Route("api/[controller]")] [ApiController] public class WebhookController : ControllerBase { private readonly StripeClient _client; private readonly string _webhookSecret; public WebhookController() { var apiKey = Environment.GetEnvironmentVariable("STRIPE_API_KEY"); _client = new StripeClient(apiKey); _webhookSecret = Environment.GetEnvironmentVariable("WEBHOOK_SECRET"); } [HttpPost] public async Task Index() { var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); try { var thinEvent = _client.ParseThinEvent(json, Request.Headers["Stripe-Signature"], _webhookSecret); // Fetch the event data to understand the failure var baseEvent = await _client.V2.Core.Events.GetAsync(thinEvent.Id); if (baseEvent is V1BillingMeterErrorReportTriggeredEvent fullEvent) { var meter = await fullEvent.FetchRelatedObjectAsync(); var meterId = meter.Id; // Record the failures and alert your team // Add your logic here } return Ok(); } catch (StripeException e) { return BadRequest(e.Message); } } } ``` #### Verwendet `context` #### Snapshot-Ereignisse Dieser Codeausschnitt ist eine Webhook-Funktion, die so konfiguriert ist, dass sie nach empfangenen Ereignissen sucht, das Ursprungskonto gegebenenfalls erkennt, das Ereignis verarbeitet und eine `200`-Antwort zurückgibt. #### Ruby ```ruby require 'json' # 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 = Stripe::Customer.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 = Stripe::PaymentMethod.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 ``` #### Python ```python import json from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt # Define API key variables (in production, pull these from environment variables or secret manager) 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, } @csrf_exempt def my_webhook_view(request): payload = request.body event = None try: event = stripe.Event.construct_from( json.loads(payload.decode('utf-8')), stripe.api_key ) except ValueError as e: # Invalid payload return HttpResponse(status=400) # Extract context context = getattr(event, "context", None) if context is None: print("Missing context in event.") return HttpResponse(status=400) api_key = account_api_keys.get(context) if api_key is None: print(f"No API key found for context: {context}") return HttpResponse(status=400) # Handle the event if event.type == 'customer.created': customer = event.data.object try: latest_customer = stripe.Customer.retrieve(customer.id, api_key=api_key) handle_customer_created(latest_customer, context) except Exception as e: print(f"Error retrieving customer: {e}") return HttpResponse(status=500) elif event.type == 'payment_method.attached': payment_method = event.data.object try: latest_payment_method = stripe.PaymentMethod.retrieve(payment_method.id, api_key=api_key) handle_payment_method_attached(latest_payment_method, context) except Exception as e: print(f"Error retrieving payment method: {e}") return HttpResponse(status=500) else: print(f'Unhandled event type {event.type}') return HttpResponse(status=200) ``` #### Java ```java // Using the Spark framework public Object handle(Request request, Response response) { String payload = request.body(); Event event = null; try { event = ApiResource.GSON.fromJson(payload, Event.class); } catch (JsonSyntaxException e) { // Invalid payload response.status(400); return ""; } // Get context from event String context = event.getContext(); if (context == null || context.isEmpty()) { System.out.println("Missing context in event."); response.status(400); return ""; } // Define your API key variables (in production, pull from environment or secrets manager) final String ACCOUNT_123_API_KEY = "sk_test_123"; final String ACCOUNT_456_API_KEY = "sk_test_456"; Map accountApiKeys = new HashMap<>(); accountApiKeys.put("account_123", ACCOUNT_123_API_KEY); accountApiKeys.put("account_456", ACCOUNT_456_API_KEY); String apiKey = accountApiKeys.get(context); if (apiKey == null) { System.out.println("No API key found for context: " + context); response.status(400); return ""; } // Deserialize the nested object inside the event EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer(); if (!dataObjectDeserializer.getObject().isPresent()) { System.out.println("Unable to deserialize object from event."); response.status(400); return ""; } StripeObject stripeObject = dataObjectDeserializer.getObject().get(); // Set up RequestOptions with the correct API key RequestOptions requestOptions = RequestOptions.builder() .setApiKey(apiKey) .build(); try { switch (event.getType()) { case "customer.created": Customer customerEvent = (Customer) stripeObject; // Fetch the latest Customer from Stripe using the account's API key Customer latestCustomer = Customer.retrieve(customerEvent.getId(), requestOptions); handleCustomerCreated(latestCustomer, context); break; case "payment_method.attached": PaymentMethod paymentMethodEvent = (PaymentMethod) stripeObject; // Fetch the latest PaymentMethod from Stripe using the account's API key PaymentMethod latestPaymentMethod = PaymentMethod.retrieve(paymentMethodEvent.getId(), requestOptions); handlePaymentMethodAttached(latestPaymentMethod, context); break; // ... handle other event types default: System.out.println("Unhandled event type: " + event.getType()); } } catch (StripeException e) { System.out.println("Stripe API error: " + e.getMessage()); response.status(500); return ""; } response.status(200); return ""; } ``` #### Node.js ```javascript // This example uses Express to receive webhooks const express = require('express'); const app = express(); app.use(express.json({ type: 'application/json' })); // Define your API key variables (in production, load from environment variables or secrets) const ACCOUNT_123_API_KEY = 'sk_test_123'; const ACCOUNT_456_API_KEY = 'sk_test_456'; const accountApiKeys = { account_123: ACCOUNT_123_API_KEY, account_456: ACCOUNT_456_API_KEY, }; app.post('/webhook', async (request, response) => { const event = request.body; const context = event.context; if (!context) { console.error('Missing context in event'); return response.status(400).send('Missing context'); } const apiKey = accountApiKeys[context]; if (!apiKey) { console.error(`No API key found for context: ${context}`); return response.status(400).send('Unknown context'); } const stripe = Stripe(apiKey); try { switch (event.type) { case 'customer.created': { const customer = event.data.object; const latestCustomer = await stripe.customers.retrieve(customer.id); handleCustomerCreated(latestCustomer, context); break; } case 'payment_method.attached': { const paymentMethod = event.data.object; const latestPaymentMethod = await stripe.paymentMethods.retrieve(paymentMethod.id); handlePaymentMethodAttached(latestPaymentMethod, context); break; } // ... handle other event types default: console.log(`Unhandled event type ${event.type}`); } response.json({ received: true }); } catch (err) { console.error(`Error processing event: ${err.message}`); response.status(500).send('Internal error'); } }); app.listen(4242, () => console.log('Running on port 4242')); ``` #### .NET ```dotnet using System; using System.IO; using Microsoft.AspNetCore.Mvc; using Stripe; namespace workspace.Controllers { [Route("api/[controller]")] public class StripeWebHook : Controller { // Define your API key variables (these should ideally come from secure config or env vars) private const string ACCOUNT_123_API_KEY = "sk_test_123"; private const string ACCOUNT_456_API_KEY = "sk_test_456"; private readonly Dictionary accountApiKeys = new() { { "account_123", ACCOUNT_123_API_KEY }, { "account_456", ACCOUNT_456_API_KEY } }; [HttpPost] public async Task Index() { var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); try { var stripeEvent = EventUtility.ParseEvent(json); var context = stripeEvent.Context; if (string.IsNullOrEmpty(context)) { Console.WriteLine("Missing context in event"); return BadRequest(); } if (!accountApiKeys.TryGetValue(context, out var apiKey)) { Console.WriteLine($"No API key found for context: {context}"); return BadRequest(); } var requestOptions = new RequestOptions { ApiKey = apiKey }; // Handle the event if (stripeEvent.Type == Events.CustomerCreated) { var customerEvent = stripeEvent.Data.Object as Customer; if (customerEvent != null) { var customerService = new CustomerService(); var latestCustomer = await customerService.GetAsync(customerEvent.Id, null, requestOptions); HandleCustomerCreated(latestCustomer, context); } } else if (stripeEvent.Type == Events.PaymentMethodAttached) { var paymentMethodEvent = stripeEvent.Data.Object as PaymentMethod; if (paymentMethodEvent != null) { var paymentMethodService = new PaymentMethodService(); var latestPaymentMethod = await paymentMethodService.GetAsync(paymentMethodEvent.Id, null, requestOptions); HandlePaymentMethodAttached(latestPaymentMethod, context); } } else { Console.WriteLine("Unhandled event type: {0}", stripeEvent.Type); } return Ok(); } catch (StripeException e) { Console.WriteLine($"Stripe error: {e.Message}"); return BadRequest(); } } private void HandleCustomerCreated(Customer customer, string context) { Console.WriteLine($"Handled customer {customer.Id} for context {context}"); // Your custom logic here } private void HandlePaymentMethodAttached(PaymentMethod paymentMethod, string context) { Console.WriteLine($"Handled payment method {paymentMethod.Id} for context {context}"); // Your custom logic here } } } ``` #### Thin-Ereignishandler (Clover+) Verwenden Sie die Eigenschaft `context` von `EventNotification`, um das Konto für Ereignisse innerhalb Ihrer [Organisation](https://docs.stripe.com/get-started/account/orgs.md) zu identifizieren. Sie müssen den [Stripe-Context-Header](https://docs.stripe.com/context.md) manuell für alle API-Aufrufe festlegen, mit Ausnahme von `.fetchRelatedObject()` und `.fetchEvent()`, die dies automatisch für Sie übernehmen. #### 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) ``` #### Ruby ```ruby api_key = ENV.fetch("STRIPE_API_KEY", nil) webhook_secret = ENV.fetch("WEBHOOK_SECRET", nil) client = Stripe::StripeClient.new(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(nil, { stripe_context: event_notification.context }) ``` #### Typescript ```typescript const orgApiKey = process.env.STRIPE_API_KEY; const webhookSecret = process.env.WEBHOOK_SECRET; const client = new Stripe(orgApiKey); // inside your webhook handler const eventNotification = client.parseEventNotification( req.body, sig, webhookSecret ); // uses `context` automatically: await eventNotification.fetchEvent() // pass context manually for other reuqests: client.invoices.list(undefined, { stripeContext: eventNotification.context, }); ``` #### Java ```java String orgApiKey = System.getenv("STRIPE_API_KEY"); String webhookSecret = System.getenv("WEBHOOK_SECRET"); StripeClient client = new StripeClient(orgApiKey); // inside your webhook handler EventNotification notif = client.parseEventNotification(webhookBody, sigHeader, WEBHOOK_SECRET); // cast to a more specific type V1BillingMeterErrorReportTriggeredEventNotification eventNotification = (V1BillingMeterErrorReportTriggeredEventNotification) notif; // uses `context` automatically eventNotification.fetchEvent(); // pass context manually for other API requests client .v1() .invoices() .list( new RequestOptions.RequestOptionsBuilder() .setStripeContext(eventNotification.context) .build()); ``` #### PHP ```php $org_api_key = getenv('STRIPE_API_KEY'); $webhook_secret = getenv('WEBHOOK_SECRET'); $client = new \Stripe\StripeClient($org_api_key); // inside your webhook handler $event_notification = $client->parseEventNotification($webhook_body, $sig_header, $webhook_secret); // uses context automatically $event_notification->fetchEvent(); // pass context manually for other API requests $client->invoices->all(null, ["stripe_context" => $event_notification->context]); ``` #### Go ```go orgApiKey := os.Getenv("STRIPE_API_KEY") webhookSecret := os.Getenv("WEBHOOK_SECRET") client := stripe.NewClient(orgApiKey) // inside your webhook handler eventNotification, err := client.ParseEventNotification(payload, req.Header.Get("Stripe-Signature"), webhookSecret) if err != nil { fmt.Fprintf(os.Stderr, "Error reading request body: %v\n", err) w.WriteHeader(http.StatusInternalServerError) return } // cast to a more specific type switch evt := eventNotification.(type) { case *stripe.V1BillingMeterErrorReportTriggeredEventNotification: // sets `Stripe-Context` automatically evt.FetchEvent(context.TODO()) // pass context manually for other API requests client.V1Invoices.Retrieve(context.TODO(), "inv_123", &stripe.InvoiceRetrieveParams{ Params: stripe.Params{ StripeContext: evt.Context.StringPtr(), }, }) } ``` #### .NET ```csharp _client = new StripeClient(Environment.GetEnvironmentVariable("STRIPE_API_KEY")); _webhookSecret = Environment.GetEnvironmentVariable("WEBHOOK_SECRET"); // inside your webhook handler var eventNotification = client.ParseEventNotification(json, Request.Headers["Stripe-Signature"], webhookSecret); if (eventNotification is V1BillingMeterErrorReportTriggeredEventNotification notif) { // uses `context` automatically notif.fetchEvent(); // pass context manually for other API requests client.V1.Invoices.List(null, new RequestOptions { StripeContext = notif.Context, }); } ``` #### Thin-Ereignishandler (Acacia oder Basil) Bei diesem Codeausschnitt handelt es sich um eine Webhook-Funktion. Sie ist so konfiguriert, dass sie Thin-Ereignisse in einer Organisation empfängt, die Signatur überprüft, mit dem Feld `context` das Ursprungskonto ermittelt und den API-Schlüssel dieses Kontos für nachfolgende API-Aufrufe verwendet. #### Python ```python import os from flask import Flask, request, jsonify from stripe import StripeClient from stripe.events import V1BillingMeterErrorReportTriggeredEvent app = Flask(__name__) org_api_key = os.environ.get("STRIPE_API_KEY") webhook_secret = os.environ.get("WEBHOOK_SECRET") client = StripeClient(org_api_key) account_api_keys = { "account_123": os.environ.get("ACCOUNT_123_API_KEY"), "account_456": os.environ.get("ACCOUNT_456_API_KEY"), } @app.route("/webhook", methods=["POST"]) def webhook(): payload = request.data sig_header = request.headers.get("Stripe-Signature") try: thin_event = client.parse_thin_event(payload, sig_header, webhook_secret) # Retrieve the event using the org client to inspect context event = client.v2.core.events.retrieve(thin_event.id) context = getattr(event, "context", None) if not context: return jsonify(error="Missing context"), 400 account_key = account_api_keys.get(context) if not account_key: return jsonify(error="Unknown context"), 400 account_client = StripeClient(account_key) full_event = account_client.v2.core.events.retrieve(thin_event.id) if isinstance(full_event, V1BillingMeterErrorReportTriggeredEvent): meter = full_event.fetch_related_object() meter_id = meter.id # Record the failures and alert your team # Add your logic here return jsonify(success=True), 200 except Exception as e: return jsonify(error=str(e)), 400 if __name__ == "__main__": app.run(port=4242) ``` #### Ruby ```ruby require "stripe" require "sinatra" api_key = ENV.fetch("STRIPE_API_KEY", nil) webhook_secret = ENV.fetch("WEBHOOK_SECRET", nil) client = Stripe::StripeClient.new(api_key) account_api_keys = { "account_123" => ENV["ACCOUNT_123_API_KEY"], "account_456" => ENV["ACCOUNT_456_API_KEY"], } post "/webhook" do webhook_body = request.body.read sig_header = request.env["HTTP_STRIPE_SIGNATURE"] begin thin_event = client.parse_thin_event(webhook_body, sig_header, webhook_secret) event = client.v2.core.events.retrieve(thin_event.id) context = event.context halt 400 if context.nil? account_key = account_api_keys[context] halt 400 if account_key.nil? account_client = Stripe::StripeClient.new(account_key) full_event = account_client.v2.core.events.retrieve(thin_event.id) if full_event.instance_of? Stripe::V1BillingMeterErrorReportTriggeredEvent meter = full_event.fetch_related_object # Record the failures and alert your team # Add your logic here end status 200 rescue => e status 400 end end ``` #### Node.js ```javascript const express = require('express'); const {Stripe} = require('stripe'); const app = express(); const apiKey = process.env.STRIPE_API_KEY; const webhookSecret = process.env.WEBHOOK_SECRET; const client = new Stripe(apiKey); const accountApiKeys = { account_123: process.env.ACCOUNT_123_API_KEY, account_456: process.env.ACCOUNT_456_API_KEY, }; app.post('/webhook', express.raw({type: 'application/json'}), async (req, res) => { const sig = req.headers['stripe-signature']; try { const thinEvent = client.parseThinEvent(req.body, sig, webhookSecret); const event = await client.v2.core.events.retrieve(thinEvent.id); const context = event.context; if (!context) return res.status(400).send('Missing context'); const accountKey = accountApiKeys[context]; if (!accountKey) return res.status(400).send('Unknown context'); const accountClient = new Stripe(accountKey); const fullEvent = await accountClient.v2.core.events.retrieve(thinEvent.id); if (fullEvent.type === 'v1.billing.meter.error_report_triggered') { const meter = await fullEvent.fetchRelatedObject(); // Record the failures and alert your team // Add your logic here } res.sendStatus(200); } catch (err) { res.status(400).send(`Webhook Error: ${err.message}`); } }); app.listen(4242); ``` #### Java ```java import com.stripe.StripeClient; import com.stripe.events.V1BillingMeterErrorReportTriggeredEvent; import com.stripe.model.Event; import com.stripe.model.ThinEvent; import java.util.HashMap; import java.util.Map; public Object handle(Request request, Response response) { String apiKey = System.getenv("STRIPE_API_KEY"); String webhookSecret = System.getenv("WEBHOOK_SECRET"); StripeClient client = new StripeClient(apiKey); Map accountApiKeys = new HashMap<>(); accountApiKeys.put("account_123", System.getenv("ACCOUNT_123_API_KEY")); accountApiKeys.put("account_456", System.getenv("ACCOUNT_456_API_KEY")); try { String webhookBody = request.body(); String sigHeader = request.headers("Stripe-Signature"); ThinEvent thinEvent = client.parseThinEvent(webhookBody, sigHeader, webhookSecret); Event baseEvent = client.v2().core().events().retrieve(thinEvent.getId()); String context = baseEvent.getContext(); if (context == null || context.isEmpty()) { response.status(400); return ""; } String accountKey = accountApiKeys.get(context); if (accountKey == null || accountKey.isEmpty()) { response.status(400); return ""; } StripeClient accountClient = new StripeClient(accountKey); Event fullEvent = accountClient.v2().core().events().retrieve(thinEvent.getId()); if (fullEvent instanceof V1BillingMeterErrorReportTriggeredEvent) { V1BillingMeterErrorReportTriggeredEvent ev = (V1BillingMeterErrorReportTriggeredEvent) fullEvent; Object meter = ev.fetchRelatedObject(); // Record the failures and alert your team // Add your logic here } response.status(200); return ""; } catch (Exception e) { response.status(400); return ""; } } ``` #### PHP ```php getenv('ACCOUNT_123_API_KEY'), 'account_456' => getenv('ACCOUNT_456_API_KEY'), ]; $app = new \Slim\App(); $app->post('/webhook', function ($request, $response) use ($client, $webhook_secret, $accountApiKeys) { $webhook_body = $request->getBody()->getContents(); $sig_header = $request->getHeaderLine('Stripe-Signature'); try { $thin_event = $client->parseThinEvent($webhook_body, $sig_header, $webhook_secret); $event = $client->v2->core->events->retrieve($thin_event->id); $context = $event->context ?? null; if (!$context) return $response->withStatus(400); $accountKey = $accountApiKeys[$context] ?? null; if (!$accountKey) return $response->withStatus(400); $accountClient = new \Stripe\StripeClient($accountKey); $full_event = $accountClient->v2->core->events->retrieve($thin_event->id); if ($full_event instanceof \Stripe\\Events\\V1BillingMeterErrorReportTriggeredEvent) { $meter = $full_event->fetchRelatedObject(); // Record the failures and alert your team // Add your logic here } return $response->withStatus(200); } catch (\Exception $e) { return $response->withStatus(400); } }); $app->run(); ``` #### Gehen Sie zu ```go package main import ( "io" "log/slog" "net/http" "os" "github.com/stripe/stripe-go/v82" ) func main() { apiKey := os.Getenv("STRIPE_API_KEY") webhookSecret := os.Getenv("WEBHOOK_SECRET") client := stripe.NewClient(apiKey) accountApiKeys := map[string]string{ "account_123": os.Getenv("ACCOUNT_123_API_KEY"), "account_456": os.Getenv("ACCOUNT_456_API_KEY"), } http.HandleFunc("/webhook", func(w http.ResponseWriter, req *http.Request) { defer req.Body.Close() payload, err := io.ReadAll(req.Body) if err != nil { slog.Error("read body", "error", err) w.WriteHeader(http.StatusInternalServerError) return } thinEvent, err := client.ParseThinEvent(payload, req.Header.Get("Stripe-Signature"), webhookSecret) if err != nil { w.WriteHeader(http.StatusBadRequest) return } baseEvent, err := client.V2.Core.Events.Retrieve(thinEvent.ID) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } if baseEvent.Context == "" { w.WriteHeader(http.StatusBadRequest) return } accountKey, ok := accountApiKeys[baseEvent.Context] if !ok || accountKey == "" { w.WriteHeader(http.StatusBadRequest) return } accountClient := stripe.NewClient(accountKey) fullEvent, err := accountClient.V2.Core.Events.Retrieve(thinEvent.ID) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } switch e := fullEvent.(type) { case *stripe.V1BillingMeterErrorReportTriggeredEvent: meter, err := e.FetchRelatedObject() if err == nil { _ = meter // Record the failures and alert your team // Add your logic here } } w.WriteHeader(http.StatusOK) }) http.ListenAndServe(":4242", nil) } ``` #### .NET ```csharp using Microsoft.AspNetCore.Mvc; using Stripe; [ApiController] [Route("/webhook")] public class WebhookController : ControllerBase { private readonly StripeClient _client; private readonly string _webhookSecret; private readonly Dictionary _accountApiKeys; public WebhookController() { _client = new StripeClient(Environment.GetEnvironmentVariable("STRIPE_API_KEY")); _webhookSecret = Environment.GetEnvironmentVariable("WEBHOOK_SECRET"); _accountApiKeys = new Dictionary { { "account_123", Environment.GetEnvironmentVariable("ACCOUNT_123_API_KEY") }, { "account_456", Environment.GetEnvironmentVariable("ACCOUNT_456_API_KEY") }, }; } [HttpPost] public async Task Handle() { using var reader = new StreamReader(Request.Body); var json = await reader.ReadToEndAsync(); try { var thinEvent = _client.ParseThinEvent(json, Request.Headers["Stripe-Signature"], _webhookSecret); var baseEvent = await _client.V2.Core.Events.GetAsync(thinEvent.Id); if (string.IsNullOrEmpty(baseEvent.Context)) { return BadRequest(); } if (!_accountApiKeys.TryGetValue(baseEvent.Context, out var accountKey) || string.IsNullOrEmpty(accountKey)) { return BadRequest(); } var accountClient = new StripeClient(accountKey); var fullEvent = await accountClient.V2.Core.Events.GetAsync(thinEvent.Id); if (fullEvent is V1BillingMeterErrorReportTriggeredEvent ev) { var meter = await ev.FetchRelatedObjectAsync(); // Record the failures and alert your team // Add your logic here } return Ok(); } catch { return BadRequest(); } } } ``` ## Handler testen Bevor Sie mit Ihrer Webhook-Endpoint-Funktion live gehen, empfehlen wir Ihnen, Ihre Anwendungsintegration zu testen. Konfigurieren Sie dazu einen lokalen Listener zum Senden von Ereignissen an Ihren lokalen Computer und senden Sie Testereignisse. Zum Testen müssen Sie die [CLI](https://docs.stripe.com/stripe-cli.md) verwenden. #### Ereignisse an einen lokalen Endpoint weiterleiten Um Ereignisse an Ihren lokalen Endpoint weiterzuleiten, führen Sie den folgenden Befehl mit der [CLI](https://docs.stripe.com/stripe-cli.md) aus und richten einen lokalen Listener ein. Das Flag `--forward-to` sendet alle [Stripe-Ereignisse](https://docs.stripe.com/cli/trigger#trigger-event) im in einer [Sandbox](https://docs.stripe.com/sandboxes.md) an Ihren lokalen Webhook-Endpoint. Verwenden Sie die entsprechenden CLI-Befehle unten, je nachdem, ob Sie [Thin](https://docs.stripe.com/event-destinations.md#events-overview) oder Snapshot-Ereignisse nutzen. #### Snapshot-Ereignisse weiterleiten Verwenden Sie den folgenden Befehl, um [Snapshot-Ereignisse](https://docs.stripe.com/event-destinations.md#events-overview) an Ihren lokalen Listener weiterzuleiten. ```bash stripe listen --forward-to localhost:4242 ``` #### Thin-Ereignisse weiterleiten Verwenden Sie den folgenden Befehl, um [Thin-Ereignisse](https://docs.stripe.com/event-destinations.md#events-overview) an Ihren lokalen Listener weiterzuleiten. ```bash $ stripe listen --forward-thin-to localhost:4242 --thin-events "*" ``` > Sie können auch den Befehl `stripe listen` ausführen, um Ereignisse in der [Stripe Shell](https://docs.stripe.com/stripe-shell/overview.md) anzuzeigen, obwohl Sie keine Ereignisse von der Shell an Ihren lokalen Endpoint weiterleiten können. Zu den nützlichen Konfigurationen, die Ihnen beim Testen mit Ihrem lokalen Listener helfen, gehören die folgenden: - Um die Verifizierung des HTTPS-Zertifikats zu deaktivieren, verwenden Sie das optionale Flag `--skip-verify`. - Um nur bestimmte Ereignisse weiterzuleiten, verwenden Sie das optionale Flag `--events` und übergeben Sie eine durch Kommas getrennte Liste von Ereignissen. #### Ziel-Snapshot-Ereignisse weiterleiten Verwenden Sie den folgenden Befehl, um Ziel-Snapshot-Ereignisse an Ihren lokalen Listener weiterzuleiten. ```bash stripe listen --events payment_intent.created,customer.created,payment_intent.succeeded,checkout.session.completed,payment_intent.payment_failed \ --forward-to localhost:4242 ``` #### Weiterleitung von Ziel-Thin-Ereignissen Verwenden Sie den folgenden Befehl, um Ziel-Thin-Ereignisse an Ihren lokalen Listener weiterzuleiten. ```bash stripe listen --thin-events v1.billing.meter.error_report_triggered,v1.billing.meter.no_meter_found \ --forward-thin-to localhost:4242 ``` - Um Ereignisse von dem öffentlichen Webhook-Endpoint, den Sie bereits bei Stripe registriert haben, an Ihren lokalen Webhook-Endpoint weiterzuleiten, verwenden Sie das optionale Flag `--load-from-webhooks-api`. Es lädt Ihren registrierten Endpoint, analysiert den Pfad und die registrierten Ereignisse und hängt dann den Pfad an Ihren lokalen Webhook-Endpoint im `--forward-to path` an. #### Snapshot-Ereignisse von einem öffentlichen Webhook-Endpoint weiterleiten Verwenden Sie den folgenden Befehl, um Snapshot-Ereignisse von einem öffentlichen Webhook-Endpoint an Ihren lokalen Listener weiterzuleiten. ```bash stripe listen --load-from-webhooks-api --forward-to localhost:4242 ``` #### Thin-Ereignisse von einem öffentlichen Webhook-Endpoint weiterleiten Verwenden Sie den folgenden Befehl, um Thin-Ereignisse von einem öffentlichen Webhook-Endpoint an Ihren lokalen Listener weiterzuleiten. ```bash stripe listen --load-from-webhooks-api --forward-thin-to localhost:4242 ``` - Verwenden Sie zum Überprüfen von Webhook-Signaturen `{{WEBHOOK_SIGNING_SECRET}}` aus der ursprünglichen Ausgabe des Befehls „listen“. ```output Ready! Your webhook signing secret is '{{WEBHOOK_SIGNING_SECRET}}' (^C to quit) ``` #### Auslösen von Testereignissen Um Testereignisse zu senden, lösen Sie einen Ereignistyp aus, den Ihr Ereignisziel abonniert hat, indem Sie manuell ein Objekt im Stripe-Dashboard erstellen. Erfahren Sie, wie Sie Ereignisse mit [Stripe für VS-Code](https://docs.stripe.com/stripe-vscode.md) auslösen können. #### Snapshot-Ereignis auslösen Sie können den folgenden Befehl entweder in der [Stripe-Shell](https://docs.stripe.com/stripe-shell/overview.md) oder der [Stripe-CLI](https://docs.stripe.com/stripe-cli.md) verwenden: Dieses Beispiel löst das Ereignis `payment_intent.succeeded` aus: ```bash stripe trigger payment_intent.succeeded Running fixture for: payment_intent Trigger succeeded! Check dashboard for event details. ``` #### Thin-Ereignis auslösen Sie können den folgenden Befehl in der [Stripe-CLI](https://docs.stripe.com/stripe-cli.md) verwenden. Dieses Beispiel löst das Ereignis `outbound_payment.posted` aus: ```bash stripe preview trigger outbound_payment.posted Setting up fixture for: finaddr_info Running fixture for: finaddr_info Setting up fixture for: create_recipient Running fixture for: create_recipient Setting up fixture for: create_destination Running fixture for: create_destination Setting up fixture for: create_outbound_payment Running fixture for: create_outbound_payment ``` ## Ihren Endpoint registrieren Nachdem Sie Ihre Webhook-Endpoint-Funktion getestet haben, verwenden Sie die [API](https://docs.stripe.com/api/v2/event-destinations.md) oder die Registerkarte **Webhooks** in Workbench zum Registrieren der URL Ihres Webhook-Endpoints, um sicherzustellen, dass weiß, wohin Ereignisse gesendet werden sollen. Sie können bis zu 16 Webhook-Endpoints bei Stripe registrieren. Registrierte Webhook-Endpoints müssen öffentlich zugängliche **HTTPS**-URLs sein. #### Webhook-URL-Format Das URL-Format zum Registrieren eines Webhook-Endpoints ist: ``` https:/// ``` Wenn Ihre Domain beispielsweise `https://mycompanysite.com` ist und die Route zu Ihrem Webhook-Endpoint `@app.route('/stripe_webhooks', methods=['POST'])` lautet, geben Sie `https://mycompanysite.com/stripe_webhooks` als **Endpoint-URL** an. #### Ein Ereignisziel für Ihren Webhook-Endpoint erstellen Erstellen Sie ein Ereignisziel mit Workbench im Dashboard oder programmgesteuert mit der [API](https://docs.stripe.com/api/v2/event-destinations.md). Sie können bis zu 16 Ereignisziele für jedes Stripe-Konto registrieren. #### Dashboard So erstellen Sie einen neuen Webhook-Endpoint im Dashboard: 1. Öffnen Sie die Registerkarte [Webhooks](https://dashboard.stripe.com/webhooks) in Workbench. 1. Klicken Sie auf **Ereignisziel erstellen**. 1. Wählen Sie aus, von wo Sie Ereignisse empfangen möchten. Stripe unterstützt zwei Arten von Konfigurationen: **Ihr Konto** und [Verbundene Konten](https://docs.stripe.com/connect.md). Wählen Sie **Konto** aus, um Ereignisse von Ihrem eigenen Konto aus zu überwachen. Wenn Sie eine [Connect-Anwendung](https://docs.stripe.com/connect.md) erstellt haben und Ereignisse von Ihren verbundenen Konten überwachen möchten, wählen Sie **Verbundene Konten** aus. > #### Ereignisse von einem Organisations-Webhook-Endpoint überwachen > > Wenn Sie einen Webhook-Endpoint in einem [Organisationskonto](https://docs.stripe.com/get-started/account/orgs.md) erstellen, wählen Sie **Konten** aus, um Ereignisse von Konten in Ihrer Organisation zu überwachen. Wenn Ihre Organisationen [Connect-Plattformen](https://docs.stripe.com/connect.md) als Mitglieder haben und Ereignisse von allen verbundenen Konten der Plattformen überwachen möchten, wählen Sie **Verbundene Konten** aus. 1. Wählen Sie die API-Version für das [Ereignisobjekt](https://docs.stripe.com/api/events.md) aus, das Sie verwenden möchten. 1. Wählen Sie die [Ereignistypen](https://docs.stripe.com/api/events/types.md) aus, die Sie an einen Webhook-Endpoint senden möchten. 1. Wählen Sie **Weiter** und dann **Webhook-Endpoint** als Zieltyp aus. 1. Klicken Sie auf **Weiter** und geben Sie dann die **Endpoint-URL** und eine optionale Beschreibung für den Webhook ein. ![Einen neuen Webhook über die Registerkarte Webhooks registrieren](https://b.stripecdn.com/docs-statics-srv/assets/create-webhook.f728025897e9e4ca2ba623abe34995a0.png) Einen neuen Webhook über die Registerkarte **Webhooks** registrieren #### API Sie können ein neues Ereignisziel erstellen, das Sie über die [API](https://docs.stripe.com/api/v2/event-destinations.md) benachrichtigt, wenn ein [nutzungsbasierter](https://docs.stripe.com/billing/subscriptions/usage-based.md) Validierungsfehler in der Abrechnung ausgelöst wird. Wenn Sie eine [Connect-Anwendung](https://docs.stripe.com/connect.md) erstellt haben und Ihre verbundenen Konten überwachen möchten, verwenden Sie den Parameter [events_from](https://docs.stripe.com/api/v2/event-destinations/create.md#v2_create_event_destinations-events_from) und legen Sie den ENUM-Wert auf `accounts` fest. ```curl curl -X POST https://api.stripe.com/v2/core/event_destinations \ -H "Authorization: Bearer <>" \ -H "Stripe-Version: {{STRIPE_API_VERSION}}" \ --json '{ "name": "My event destination", "description": "This is my event destination, I like it a lot", "type": "webhook_endpoint", "event_payload": "thin", "enabled_events": [ "v1.billing.meter.error_report_triggered" ], "webhook_endpoint": { "url": "https://example.com/my/webhook/endpoint" } }' ``` ```cli stripe v2 core event_destinations create \ --name="My event destination" \ --description="This is my event destination, I like it a lot" \ --type=webhook_endpoint \ --event-payload=thin \ --enabled-events="v1.billing.meter.error_report_triggered" \ --webhook-endpoint.url="https://example.com/my/webhook/endpoint" ``` ```ruby # Set your secret key. Remember to switch to your live secret key in production. # See your keys here: https://dashboard.stripe.com/apikeys client = Stripe::StripeClient.new("<>") event_destination = client.v2.core.event_destinations.create({ name: 'My event destination', description: 'This is my event destination, I like it a lot', type: 'webhook_endpoint', event_payload: 'thin', enabled_events: ['v1.billing.meter.error_report_triggered'], webhook_endpoint: {url: 'https://example.com/my/webhook/endpoint'}, }) ``` ```python # Set your secret key. Remember to switch to your live secret key in production. # See your keys here: https://dashboard.stripe.com/apikeys client = StripeClient("<>") event_destination = client.v2.core.event_destinations.create({ "name": "My event destination", "description": "This is my event destination, I like it a lot", "type": "webhook_endpoint", "event_payload": "thin", "enabled_events": ["v1.billing.meter.error_report_triggered"], "webhook_endpoint": {"url": "https://example.com/my/webhook/endpoint"}, }) ``` ```php // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys $stripe = new \Stripe\StripeClient('<>'); $eventDestination = $stripe->v2->core->eventDestinations->create([ 'name' => 'My event destination', 'description' => 'This is my event destination, I like it a lot', 'type' => 'webhook_endpoint', 'event_payload' => 'thin', 'enabled_events' => ['v1.billing.meter.error_report_triggered'], 'webhook_endpoint' => ['url' => 'https://example.com/my/webhook/endpoint'], ]); ``` ```java // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys StripeClient client = new StripeClient("<>"); EventDestinationCreateParams params = EventDestinationCreateParams.builder() .setName("My event destination") .setDescription("This is my event destination, I like it a lot") .setType(EventDestinationCreateParams.Type.WEBHOOK_ENDPOINT) .setEventPayload(EventDestinationCreateParams.EventPayload.THIN) .addEnabledEvent("v1.billing.meter.error_report_triggered") .setWebhookEndpoint( EventDestinationCreateParams.WebhookEndpoint.builder() .setUrl("https://example.com/my/webhook/endpoint") .build() ) .build(); EventDestination eventDestination = client.v2().core().eventDestinations().create(params); ``` ```node // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys const stripe = require('stripe')('<>'); const eventDestination = await stripe.v2.core.eventDestinations.create({ name: 'My event destination', description: 'This is my event destination, I like it a lot', type: 'webhook_endpoint', event_payload: 'thin', enabled_events: ['v1.billing.meter.error_report_triggered'], webhook_endpoint: { url: 'https://example.com/my/webhook/endpoint', }, }); ``` ```go // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys sc := stripe.NewClient("<>") params := &stripe.V2CoreEventDestinationCreateParams{ Name: stripe.String("My event destination"), Description: stripe.String("This is my event destination, I like it a lot"), Type: stripe.String("webhook_endpoint"), EventPayload: stripe.String("thin"), EnabledEvents: []*string{stripe.String("v1.billing.meter.error_report_triggered")}, WebhookEndpoint: &stripe.V2CoreEventDestinationCreateWebhookEndpointParams{ URL: stripe.String("https://example.com/my/webhook/endpoint"), }, } result, err := sc.V2CoreEventDestinations.Create(context.TODO(), params) ``` ```dotnet // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys var options = new Stripe.V2.Core.EventDestinationCreateOptions { Name = "My event destination", Description = "This is my event destination, I like it a lot", Type = "webhook_endpoint", EventPayload = "thin", EnabledEvents = new List { "v1.billing.meter.error_report_triggered" }, WebhookEndpoint = new Stripe.V2.Core.EventDestinationCreateWebhookEndpointOptions { Url = "https://example.com/my/webhook/endpoint", }, }; var client = new StripeClient("<>"); var service = client.V2.Core.EventDestinations; Stripe.V2.Core.EventDestination eventDestination = service.Create(options); ``` > [Workbench](https://docs.stripe.com/workbench.md) ersetzt das bestehende [Entwickler-Dashboard](https://docs.stripe.com/development/dashboard.md). Wenn Sie immer noch das Entwickler-Dashboard verwenden, finden Sie hier weitere Informationen zum [Erstellen eines neuen Webhook-Endpoints](https://docs.stripe.com/development/dashboard/webhooks.md). ## Endpoint sichern Nachdem Sie bestätigt haben, dass Ihr Endpoint wie erwartet funktioniert, sichern Sie ihn, indem Sie [Best Practices für Webhooks](https://docs.stripe.com/webhooks.md#best-practices) implementieren. Sie müssen Ihre Integration sichern, indem Sie dafür sorgen, dass Ihr Handler verifiziert, dass alle Webhook-Anfragen von Stripe generiert wurden. Sie können Webhook-Signaturen mit unseren offiziellen Bibliotheken verifizieren oder manuell. #### Verifizierung mit offiziellen Bibliotheken (empfohlen) ### Webhook-Signaturen mit offiziellen Bibliotheken verifizieren Wir empfehlen die Verwendung unserer offiziellen Bibliotheken, um Signaturen zu überprüfen. Sie führen die Verifizierung durch, indem Sie die Ereignisnutzlast, den `Stripe-Signature`-Header und das Geheimnis des Endpoints angeben. Wenn die Verifizierung fehlschlägt, erhalten Sie eine Fehlermeldung. Wenn Sie einen Fehler bei der Signaturüberprüfung erhalten, lesen Sie unseren Leitfaden zur [Fehlerbehebung](https://docs.stripe.com/webhooks/signature.md). > Stripe benötigt den Rohtext der Anfrage, um die Signaturprüfung durchzuführen. Wenn Sie ein Framework verwenden, müssen Sie sicherstellen, dass der Rohtext nicht manipuliert wird. Jegliche Manipulation des Rohtextes der Anfrage führt dazu, dass die Verifizierung fehlschlägt. #### Ruby ```ruby # Set your secret key. Remember to switch to your live secret key in production. # See your keys here: https://dashboard.stripe.com/apikeys Stripe.api_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 Dashboardendpoint_secret = 'whsec_...' # Using the Sinatra framework set :port, 4242 post '/my/webhook/url' do payload = request.body.readsig_header = request.env['HTTP_STRIPE_SIGNATURE'] event = nil beginevent = 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 ``` #### Python ```python # Set your secret key. Remember to switch to your live secret key in production. # See your keys here: https://dashboard.stripe.com/apikeys stripe.api_key = '<>' from django.http import HttpResponse # 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 Dashboardendpoint_secret = 'whsec_...' # Using Django @csrf_exempt def my_webhook_view(request): payload = request.bodysig_header = request.META['HTTP_STRIPE_SIGNATURE'] event = None try:event = stripe.Webhook.construct_event( payload, sig_header, endpoint_secret ) except ValueError as e: # Invalid payload print('Error parsing payload: {}'.format(str(e))) return HttpResponse(status=400)except stripe.error.SignatureVerificationError as e: # Invalid signature print('Error verifying webhook signature: {}'.format(str(e))) return HttpResponse(status=400) # Handle the event if event.type == 'payment_intent.succeeded': payment_intent = event.data.object # contains a stripe.PaymentIntent print('PaymentIntent was successful!') elif event.type == 'payment_method.attached': payment_method = event.data.object # contains a stripe.PaymentMethod print('PaymentMethod was attached to a Customer!') # ... handle other event types else: print('Unhandled event type {}'.format(event.type)) return HttpResponse(status=200) ``` #### PHP ```php // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys \Stripe\Stripe::setApiKey('<>'); // 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_...'; $payload = @file_get_contents('php://input'); $sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE']; $event = null; try {$event = \Stripe\Webhook::constructEvent( $payload, $sig_header, $endpoint_secret ); } catch(\UnexpectedValueException $e) { // Invalid payload http_response_code(400); echo json_encode(['Error parsing payload: ' => $e->getMessage()]); exit();} catch(\Stripe\Exception\SignatureVerificationException $e) { // Invalid signature http_response_code(400); echo json_encode(['Error verifying webhook signature: ' => $e->getMessage()]); exit(); } // Handle the event switch ($event->type) { case 'payment_intent.succeeded': $paymentIntent = $event->data->object; // contains a \Stripe\PaymentIntent handlePaymentIntentSucceeded($paymentIntent); break; case 'payment_method.attached': $paymentMethod = $event->data->object; // contains a \Stripe\PaymentMethod handlePaymentMethodAttached($paymentMethod); break; // ... handle other event types default: echo 'Received unknown event type ' . $event->type; } http_response_code(200); ``` #### Java ```java // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys Stripe.apiKey = "<>"; import com.stripe.Stripe; import com.stripe.model.StripeObject; import com.stripe.net.ApiResource; import com.stripe.net.Webhook; import com.stripe.model.Event; import com.stripe.model.EventDataObjectDeserializer; import com.stripe.model.PaymentIntent; import com.stripe.exception.SignatureVerificationException; // 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 DashboardString endpointSecret = "whsec_..."; // Using the Spark framework public Object handle(Request request, Response response) { String payload = request.body();String sigHeader = request.headers("Stripe-Signature"); Event event = null; try {event = Webhook.constructEvent( payload, sigHeader, endpointSecret ); } catch (JsonSyntaxException e) { // Invalid payload System.out.println("Error parsing payload: " + e.getMessage()); response.status(400); return gson.toJson(new ErrorResponse(e.getMessage()));} catch (SignatureVerificationException e) { // Invalid signature System.out.println("Error verifying webhook signature: " + e.getMessage()); response.status(400); return gson.toJson(new ErrorResponse(e.getMessage())); } // Deserialize the nested object inside the event EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer(); StripeObject stripeObject = null; if (dataObjectDeserializer.getObject().isPresent()) { stripeObject = dataObjectDeserializer.getObject().get(); } else { // Deserialization failed, probably due to an API version mismatch. // Refer to the Javadoc documentation on `EventDataObjectDeserializer` for // instructions on how to handle this case, or return an error here. } // Handle the event switch (event.getType()) { case "payment_intent.succeeded": PaymentIntent paymentIntent = (PaymentIntent) stripeObject; System.out.println("PaymentIntent was successful!"); break; case "payment_method.attached": PaymentMethod paymentMethod = (PaymentMethod) stripeObject; System.out.println("PaymentMethod was attached to a Customer!"); break; // ... handle other event types default: System.out.println("Unhandled event type: " + event.getType()); } response.status(200); return ""; } ``` #### Node.js ```javascript // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys const stripe = require('stripe')('<>'); // 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 Dashboardconst endpointSecret = 'whsec_...'; // This example uses Express to receive webhooks const express = require('express'); const app = express(); // Match the raw body to content type application/json app.post('/webhook', express.raw({type: 'application/json'}), (request, response) => {const sig = request.headers['stripe-signature']; let event; try {event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret); }catch (err) { response.status(400).send(`Webhook Error: ${err.message}`); } // Handle the event switch (event.type) { case 'payment_intent.succeeded': const paymentIntent = event.data.object; console.log('PaymentIntent was successful!'); break; case 'payment_method.attached': const paymentMethod = event.data.object; console.log('PaymentMethod was attached to a Customer!'); break; // ... handle other event types default: console.log(`Unhandled event type ${event.type}`); } // Return a response to acknowledge receipt of the event response.json({received: true}); }); app.listen(4242, () => console.log('Running on port 4242')); ``` #### Go ```go // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys stripe.Key = "<>" http.HandleFunc("/webhook", func(w http.ResponseWriter, req *http.Request) { const MaxBodyBytes = int64(65536) req.Body = http.MaxBytesReader(w, req.Body, MaxBodyBytes) payload, err := ioutil.ReadAll(req.Body) if err != nil { fmt.Fprintf(os.Stderr, "Error reading request body: %v\n", err) w.WriteHeader(http.StatusServiceUnavailable) return } // 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 DashboardendpointSecret := "whsec_..."; // Pass the request body and Stripe-Signature header to ConstructEvent, along // with the webhook signing key.event, err := webhook.ConstructEvent(payload, req.Header.Get("Stripe-Signature"), endpointSecret) if err != nil { fmt.Fprintf(os.Stderr, "Error verifying webhook signature: %v\n", err) w.WriteHeader(http.StatusBadRequest) // Return a 400 error on a bad signature return } // Unmarshal the event data into an appropriate struct depending on its Type switch event.Type { case "payment_intent.succeeded": var paymentIntent stripe.PaymentIntent err := json.Unmarshal(event.Data.Raw, &paymentIntent) if err != nil { fmt.Fprintf(os.Stderr, "Error parsing webhook JSON: %v\n", err) w.WriteHeader(http.StatusBadRequest) return } fmt.Println("PaymentIntent was successful!") case "payment_method.attached": var paymentMethod stripe.PaymentMethod err := json.Unmarshal(event.Data.Raw, &paymentMethod) if err != nil { fmt.Fprintf(os.Stderr, "Error parsing webhook JSON: %v\n", err) w.WriteHeader(http.StatusBadRequest) return } fmt.Println("PaymentMethod was attached to a Customer!") // ... handle other event types default: fmt.Fprintf(os.Stderr, "Unhandled event type: %s\n", event.Type) } w.WriteHeader(http.StatusOK) }) ``` #### .NET ```dotnet // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys StripeConfiguration.ApiKey = "<>"; using System; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Stripe; namespace workspace.Controllers { [Route("api/[controller]")] public class StripeWebHook : Controller { // 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 Dashboardconst string endpointSecret = "whsec_..."; [HttpPost] public async Task Index() { var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); try {var stripeEvent = EventUtility.ConstructEvent(json, Request.Headers["Stripe-Signature"], endpointSecret); // Handle the event // If on SDK version < 46, use class Events instead of EventTypes if (stripeEvent.Type == EventTypes.PaymentIntentSucceeded) { var paymentIntent = stripeEvent.Data.Object as PaymentIntent; Console.WriteLine("PaymentIntent was successful!"); } else if (stripeEvent.Type == EventTypes.PaymentMethodAttached) { var paymentMethod = stripeEvent.Data.Object as PaymentMethod; Console.WriteLine("PaymentMethod was attached to a Customer!"); } // ... handle other event types else { Console.WriteLine("Unhandled event type: {0}", stripeEvent.Type); } return Ok(); }catch (StripeException e) { return BadRequest(e.Message); } } } } ``` #### Manuell verifizieren ### Webhook-Signaturen manuell verifizieren Obwohl wir empfehlen, unsere offiziellen Bibliotheken zu verwenden, um Webhook-Ereignissignaturen zu überprüfen, können Sie mithilfe dieses Abschnitts eine nutzerdefinierte Lösung erstellen. Der `Stripe-Signature`-Header in jedem signierten Ereignis enthält einen Zeitstempel und eine oder mehrere Signaturen, die Sie überprüfen müssen. Dem Zeitstempel wird `t=` vorangestellt und jeder Signatur wird ein *Schema* vorangestellt. Schemata beginnen mit `v`, gefolgt von einer ganzen Zahl. Derzeit ist `v1` die einzige gültige Live-Signatur. Als Hilfe bei Tests sendet Stripe eine zusätzliche Signatur mit einem falschen `v0`-Schema für Ereignisse im Test-Modus. ``` Stripe-Signature: t=1492774577, v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd, v0=6ffbb59b2300aae63f272406069a9788598b792a944a07aba816edb039989a39 ``` > Wir haben der Übersichtlichkeit halber neue Zeilen eingefügt, aber ein echter `Stripe-Signature`-Header befindet sich in einer einzigen Zeile. Stripe generiert Signaturen mithilfe eines Hash-basierten Nachrichtenauthentifizierungscodes ([HMAC](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code)) mit [SHA-256](https://en.wikipedia.org/wiki/SHA-2). Um [Downgrade-Angriffe](https://en.wikipedia.org/wiki/Downgrade_attack) zu verhindern, ignorieren Sie alle Schemata, die nicht `v1` sind. Sie können mehrere Signaturen mit demselben Schema-Geheimschlüssel-Paar haben, wenn Sie den [Geheimschlüssel eines Endpoints neu generieren](https://docs.stripe.com/webhooks.md#roll-endpoint-secrets) und den vorherigen Geheimschlüssel bis zu 24 Stunden aktiv lassen. Während dieses Zeitraums verfügt Ihr Endpoint über mehrere aktive Geheimschlüssel und Stripe generiert für jeden dieser Geheimschlüssel eine Signatur. Um eine manuelle Lösung zum Verifizieren von Signaturen zu erstellen, müssen Sie die folgenden Schritte ausführen: #### Schritt 1: Entfernen Sie den Zeitstempel und die Signaturen aus der Kopfzeile Teilen Sie die Kopfzeile mit dem Zeichen `,` auf, um eine Liste der Elemente zu erhalten. Teilen Sie dann jedes Element mit dem Zeichen `=` als Trennzeichen auf, um ein Präfix und ein Wertpaar zu erhalten. Der Wert des Präfixes `t` entspricht dem Zeitstempel und `v1` entspricht der Signatur (oder den Signaturen). Sie können alle anderen Elemente verwerfen. #### Schritt 2: Bereiten Sie die Zeichenfolge `signed_payload` vor Die Zeichenfolge `signed_payload` wird durch Verketten folgender Elemente erstellt: - Zeitstempel (als Zeichenfolge) - Das Zeichen `.` - Die tatsächliche JSON-Nutzlast (also der Anfragetext) #### Schritt 3: Die erwartete Signatur ermitteln Berechnen Sie einen HMAC mit der SHA256-Hash-Funktion. Verwenden Sie den Signatur-Geheimschlüssel des Endpoints als Schlüssel und die Zeichenfolge `signed_payload` als Nachricht. #### Schritt 4: Vergleichen Sie die Signaturen Vergleichen Sie die Signatur (oder Signaturen) in der Kopfzeile. Für eine identische Übereinstimmung, berechnen Sie die Differenz zwischen dem aktuellen und dem empfangenen Zeitstempel und entscheiden Sie dann, ob die Differenz in Ihrem Toleranzbereich liegt. Zum Schutz vor Timing-Angriffen verwenden Sie einen konstanten Zeit-String-Vergleich, um die erwartete Signatur mit jeder der empfangenen Signaturen zu vergleichen. ## Webhook-Integrationen debuggen Bei der Übermittlung von Ereignissen an Ihren Webhook-Endpoint können mehrere Arten von Problemen auftreten: - Stripe kann ein Ereignis möglicherweise nicht an Ihren Webhook-Endpoint übermitteln. - Bei Ihrem Webhook-Endpoint besteht möglicherweise ein SSL-Problem. - Ihre Netzwerkverbindung ist unterbrochen. - Ihr Webhook-Endpoint empfängt die von Ihnen erwarteten Ereignisse nicht. ### Ereignisübermittlungen anzeigen Sie können auch die [Stripe-CLI](https://docs.stripe.com/stripe-cli.md) verwenden, um direkt in Ihrem Datenterminal [Ereignisse zu überwachen](https://docs.stripe.com/webhooks.md#test-webhook). Um Ereignisübermittlungen anzuzeigen, wählen Sie den Webhook-Endpoint unter **Webhooks** und dann die Registerkarte **Ereignisse** aus. Die Registerkarte **Ereignisse** enthält eine Liste der Ereignisse und deren Status `Delivered`, `Pending` oder `Failed`. Klicken Sie auf ein Ereignis, um Metadaten anzuzeigen, einschließlich des HTTP-Statuscodes des Zustellversuchs und des Zeitpunkts ausstehender zukünftiger Zustellungen. ### HTTP-Statuscodes korrigieren Wenn ein Ereignis den Statuscode `200` anzeigt, bedeutet dies eine erfolgreiche Zustellung an den Webhook-Endpoint. Möglicherweise erhalten Sie auch einen anderen Statuscode als `200`. In der folgenden Tabelle finden Sie eine Liste gängiger HTTP-Statuscodes und empfohlener Lösungen. | Webhook-Status ausstehend | Beschreibung | Korrigieren | | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | (Verbindung nicht möglich) FHLR | Wir können keine Verbindung zum Zielserver herstellen. | Stellen Sie sicher, dass Ihre Host-Domain im Internet öffentlich zugänglich ist. | | (`302`) FHLR (oder ein anderer `3xx`-Status) | Der Zielserver hat versucht, die Anfrage an einen anderen Standort umzuleiten. Wir betrachten Weiterleitungsantworten auf Webhook-Anfragen als fehlgeschlagen. | Legen Sie das Webhook-Endpoint-Ziel auf die durch die Weiterleitung aufgelöste URL fest. | | (`400`) FHLR (oder ein anderer `4xx`-Status) | Der Zielserver kann oder wird die Anfrage nicht verarbeiten. Dies kann vorkommen, wenn der Server einen Fehler erkennt (`400`), wenn für die Ziel-URL Zugriffsbeschränkungen gelten (`401`, `403`) oder wenn die Ziel-URL nicht existiert (`404`). | - Stellen Sie sicher, dass Ihr Endpoint im Internet öffentlich zugänglich ist. - Stellen Sie sicher, dass Ihr Endpoint eine POST-HTTP-Methode akzeptiert. | | (`500`) FHLR (oder ein anderer `5xx`-Status) | Bei der Verarbeitung der Anfrage ist auf dem Zielserver ein Fehler aufgetreten. | Überprüfen Sie die Protokolle Ihrer Anwendung, um zu verstehen, warum der Fehler `500` zurückgegeben wird. | | (TLS-Fehler) FHLR | Wir konnten keine sichere Verbindung zum Zielserver herstellen. Diese Fehler werden in der Regel durch Probleme mit dem SSL/TLS-Zertifikat oder einem Zwischenzertifikat in der Zertifikatskette des Zielservers verursacht. Stripe erfordert die *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)-Version `v1.2` oder neuer. | Führen Sie einen [SSL-Servertest](https://www.ssllabs.com/ssltest/) durch, um Probleme zu finden, die diesen Fehler möglicherweise verursacht haben. | | (Zeit überschritten) FHLR | Die Antwort des Zielservers auf die Webhook-Anfrage dauerte zu lange. | Stellen Sie sicher, dass Sie komplexe Logik zurückstellen und sofort eine erfolgreiche Antwort in Ihrem Webhook-Verarbeitungscode zurückgeben. | ## Verhaltensweisen der Ereignisübermittlung In diesem Abschnitt erfahren Sie, welche Verhaltensweisen Sie in Bezug auf das Senden von Ereignissen durch Stripe an Ihren Webhook-Endpoint erwarten können. ### Automatische Wiederholungsversuche Stripe versucht, Ereignisse mit einem exponentiellen Backoff im Live-Modus bis zu drei Tage an Ihr Ziel zu senden. Wann der nächste Wiederholungsversuch stattfinden wird, sofern zutreffend, sehen Sie auf der Registerkarte **Ereignisübermittlungen** Ihres Ereignisziels. Wir versuchen, Ereignisse, die in einer Sandbox erstellt wurde, innerhalb weniger Stunden dreimal zu übermitteln. Wenn Ihr Ziel bei unserem Wiederholungsversuch deaktiviert oder gelöscht wurde, unternehmen wir keine zukünftigen Wiederholungsversuche für dieses Ereignis. Wenn Sie ein Ereignisziel jedoch deaktivieren und wieder reaktivieren, bevor wir einen erneuten Versuch starten können, sehen Sie nach wie vor zukünftige Wiederholungsversuche. ### Manuelle Wiederholungsversuche Es gibt zwei Möglichkeiten, Ereignisse manuell zu wiederholen: - Klicken Sie im Stripe-Dashboard auf **Erneut senden**, wenn Sie sich ein bestimmtes Ereignis ansehen. Dies funktioniert bis zu 15 Tage nach der Erstellung des Ereignisses. - Führen Sie mit der [Stripe CLI](https://docs.stripe.com/cli/events/resend) den Befehl `stripe events resend --webhook-endpoint=` aus. Dies funktioniert bis zu 30 Tage nach der Erstellung des Ereignisses. Durch das manuelle erneute Senden eines Ereignisses, das frühere Zustellungsfehler hatte, an einen Webhook-Endpoint wird das [automatische Wiederholungsverhalten](https://docs.stripe.com/webhooks.md#automatic-retries) von Stripe nicht verworfen. Automatische Wiederholungsversuche erfolgen immer noch, bis Sie auf einen davon mit einem `2xx` Status-Code antworten. ### Anordnung von Ereignissen Stripe garantiert die Übermittlung von Ereignissen nicht in der Reihenfolge, in der sie generiert wurden. Beim Erstellen eines Abonnements können beispielsweise die folgenden Ereignisse generiert werden: - `customer.subscription.created` - `invoice.created` - `invoice.paid` - `charge.created` (wenn eine Zahlung vorhanden ist) Stellen Sie sicher, dass Ihr Ereignisziel Ereignisse nicht nur in einer bestimmten Reihenfolge empfangen kann. Ihr Ziel sollte jegliche Übermittlung entsprechend verarbeiten können. Sie können fehlende Objekte auch mit der API abrufen. So können Sie beispielsweise die Objekte für Rechnung, Zahlung und Abonnement mit den Informationen aus `invoice.paid` abrufen, wenn Sie dieses Ereignis zuerst erhalten. ### API-Versionierung Die API-Version in Ihren Kontoeinstellungen beim Auftreten des Ereignisses bestimmt die API-Version und damit die Struktur eines [Ereignisses](https://docs.stripe.com/api/events.md), das an Ihr Ziel gesendet wird. Wenn für Ihr Konto beispielsweise eine ältere API-Version festgelegt ist, z. B. 16.02.2015, und Sie die API-Version mit [Versionierung](https://docs.stripe.com/api.md#versioning) für eine bestimmte Anfrage ändern, basiert das generierte und an Ihr Ziel gesendete [Ereignis](https://docs.stripe.com/api/events.md)-Objekt weiterhin auf der API-Version 2015-02-16. Sie können [Ereignis](https://docs.stripe.com/api/events.md)-Objekte nach der Erstellung nicht mehr ändern. Wenn Sie beispielsweise eine Zahlung aktualisieren, bleibt das ursprüngliche Zahlungsereignis unverändert. Nachfolgende Aktualisierungen der API-Version Ihres Kontos ändern daher vorhandene [Ereignis](https://docs.stripe.com/api/events.md)-Objekte nicht rückwirkend. Auch das Abrufen eines älteren [Ereignisses](https://docs.stripe.com/api/events.md) durch Aufrufen von `/v1/events` mithilfe einer neueren API-Version wirkt sich nicht auf die Struktur des empfangenen Ereignisses aus. Sie können Testereignisziele entweder auf Ihre Standard-API-Version oder die neueste API-Version festlegen. Das an das Ziel gesendete [Ereignis](https://docs.stripe.com/api/events.md) ist für die angegebene Version des Ereignisziels strukturiert. ## Best Practices für die Verwendung von Webhooks Überprüfen Sie diese Best Practices, um sicherzustellen, dass Ihre Webhook-Endpoints sicher bleiben und gut mit Ihrer Integration funktionieren. ### Umgang mit doppelten Ereignissen Webhook-Endpoints empfangen gelegentlich dasselbe Ereignis mehrmals. Sie können sich vor dem Erhalt doppelter Ereignisse schützen, indem Sie Ihre verarbeiteten [Ereignis-IDs](https://docs.stripe.com/api/events/object.md#event_object-id) protokollieren und bereits protokollierte Ereignisse dann nicht erneut verarbeiten. In einigen Fällen werden zwei separate Ereignisobjekte generiert und gesendet. Um diese Duplikate zu identifizieren, verwenden Sie die ID des Objekts in `data.object` zusammen mit dem `event.type`. ### Nur die Ereignistypen überwachen, die Ihre Integration erfordert Konfigurieren Sie Ihre Webhook-Endpoints so, dass sie nur die für Ihre Integration erforderlichen Ereignistypen empfangen. Die Überwachung zusätzlicher Ereignisse (oder aller Ereignisse) belastet Ihren Server und wird nicht empfohlen. Sie können [die Ereignisse](https://docs.stripe.com/api/webhook_endpoints/update.md#update_webhook_endpoint-enabled_events), die ein Webhook-Endpoint empfängt, im Dashboard oder mit der API ändern. ### Ereignisse asynchron verarbeiten Konfigurieren Sie Ihren Handler so, dass eingehende Ereignisse mit einer asynchronen Warteschlange verarbeitet werden. Möglicherweise treten Skalierbarkeitsprobleme auf, wenn Sie sich für die synchrone Verarbeitung von Ereignissen entscheiden. Jeder große Anstieg bei den Webhook-Übermittlungen (z. B. zu Beginn des Monats, wenn alle Abonnements verlängert werden) kann Ihre Endpoint-Hosts überfordern. Asynchrone Warteschlangen ermöglichen es Ihnen, die gleichzeitigen Ereignisse mit einer Geschwindigkeit zu verarbeiten, die Ihr System unterstützen kann. ### Webhook-Route vom CSRF-Schutz ausgenommen Wenn Sie Rails, Django oder ein anderes Web-Framework verwenden, überprüft Ihre Website möglicherweise automatisch, ob jede POST-Anfrage ein *CSRF-Token* enthält. Dies ist eine wichtige Sicherheitsfunktion, die Sie und Ihre Nutzer/innen vor [Cross-Site-Request-Forgery](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_\(CSRF\)) -Angriffen schützt. Diese Sicherheitsmaßnahme kann Ihre Website jedoch auch daran hindern, legitime Ereignisse zu verarbeiten. In diesem Fall müssen Sie möglicherweise die Webhooks-Route vom CSRF-Schutz ausnehmen. #### 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 ``` #### Django ```python import json # Webhooks are always sent as HTTP POST requests, so ensure # that only POST requests reach your webhook view by # decorating `webhook()` with `require_POST`. # # To ensure that the webhook view can receive webhooks, # also decorate `webhook()` with `csrf_exempt`. @require_POST @csrf_exempt def webhook(request): # Process webhook data in `request.body` ``` ### Ereignisse mit einem HTTPS-Server empfangen Wenn Sie eine HTTPS-URL für Ihren Webhook-Endpoint verwenden (im Live-Modus erforderlich), validiert Stripe, dass die Verbindung zu Ihrem Server sicher ist, bevor wir Ihre Webhook-Daten senden. Damit dies funktioniert, muss der Server so konfiguriert sein, dass er HTTPS mit einem gültigen Server-Zertifikat unterstützt. Stripe-Webhooks unterstützen nur die *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)-Versionen v1.2 und v1.3. ### Geheimschlüssel für Signaturen für Endpoints in regelmäßigen Abständen neu generieren Der Geheimschlüssel, der verwendet wird, um zu überprüfen, ob Ereignisse von Stripe stammen, kann auf der Registerkarte **Webhooks** in Workbench geändert werden. Um Geheimschlüssel zu schützen, empfehlen wir sie in regelmäßigen Abständen neu zu generieren (zu ändern) oder wenn Sie vermuten, dass ein Geheimschlüssel kompromittiert wurde. So generieren Sie einen Geheimschlüssel neu: 1. Klicken Sie auf die einzelnen Endpoints auf der Workbench-Registerkarte **Webhooks**, für die Sie den Geheimschlüssel neu generieren möchten. 1. Navigieren Sie zum Überlaufmenü (⋯) und klicken Sie auf **Geheimschlüssel neu generieren**. Sie können den aktuellen Geheimschlüssel sofort ablaufen lassen oder den Ablauf um bis zu 24 Stunden verzögern, damit Sie Zeit haben, den Verifizierungscode auf Ihrem Server zu aktualisieren. Während dieses Zeitraums sind mehrere Geheimschlüssel für den Endpoint aktiv. Stripe generiert bis zum Ablauf eine Signatur pro Geheimschlüssel. ### Überprüfen, ob Ereignisse von Stripe gesendet werden Stripe sendet Webhook-Ereignisse von einer festgelegten Liste von IP-Adressen. Vertrauen Sie nur Ereignissen, die von diesen [IP-Adressen](https://docs.stripe.com/ips.md) stammen. Überprüfen Sie auch Webhook-Signaturen, um zu bestätigen, dass Stripe die empfangenen Ereignisse gesendet hat. Stripe signiert Webhook-Ereignisse, die an Ihre Endpoints gesendet werden, indem eine Signatur in den `Stripe-Signature`-Header jedes Ereignisses eingefügt wird. So können Sie überprüfen, ob die Ereignisse von Stripe und nicht von einem Drittanbieter gesendet wurden. Sie können Signaturen entweder mit unseren [offiziellen Bibliotheken](https://docs.stripe.com/webhooks.md#verify-official-libraries) verifizieren oder mit Ihrer eigenen Lösung [manuell verifizieren](https://docs.stripe.com/webhooks.md#verify-manually). Im folgenden Abschnitt wird beschrieben, wie Sie Webhook-Signaturen verifizieren: 1. Rufen Sie den Geheimschlüssel Ihres Endpoints ab. 1. Überprüfen Sie die Signatur. #### Geheimschlüssel Ihres Endpoints abrufen Verwenden Sie Workbench und gehen Sie auf die Registerkarte **Webhooks**, um alle Ihre Endpoints anzuzeigen. Wählen Sie einen Endpoint aus, für den Sie den Geheimschlüssel erfahren möchten, und klicken Sie dann auf **Klicken Sie zum Aufdecken**. Stripe generiert für jeden Endpoint einen eindeutigen Geheimschlüssel. Wenn Sie denselben Endpoint sowohl für [Test- als auch für Live-API-Schlüssel](https://docs.stripe.com/keys.md#test-live-modes) verwenden, ist der Geheimschlüssel für jeden dieser Schlüssel unterschiedlich. Wenn Sie mehrere Endpoints verwenden, müssen Sie außerdem für jeden Endpoint, für den Sie Signaturen verifizieren möchten, einen Geheimschlüssel abrufen. Nach dieser Einrichtung beginnt Stripe, jeden Webhook zu signieren, der an den Endpoint gesendet wird. ### Replay-Angriffe verhindern Bei einem [Replay-Angriff](https://en.wikipedia.org/wiki/Replay_attack) fängt ein Angreifer eine gültige Nutzlast und deren Signatur ab und überträgt sie dann erneut. Um solche Angriffe zu verhindern, fügt Stripe einen Zeitstempel in den `Stripe-Signature`-Header ein. Da der Zeitstempel zu der signierten Nutzlast gehört, ist er ebenfalls durch die Signatur verifiziert. So kann ein Angreifer den Zeitstempel nicht ändern, ohne dass die Signatur ungültig wird. Wenn die Signatur gültig, der Zeitstempel aber zu alt ist, kann Ihre Anwendung die Nutzlast ablehnen. Unsere Bibliotheken haben eine Standardtoleranz von 5 Minuten zwischen dem Zeitstempel und der aktuellen Zeit. Sie können diese Toleranz ändern, indem Sie bei der Überprüfung von Signaturen einen zusätzlichen Parameter angeben. Verwenden Sie das Network Time Protocol ([NTP](https://en.wikipedia.org/wiki/Network_Time_Protocol)), um sicherzustellen, dass die Uhrzeit Ihres Servers korrekt ist und mit der Zeit auf den Servern von Stripe synchronisiert wird. > Verwenden Sie keinen Toleranzwert von `0`.Mit einem Toleranzwert von `0` wird die Aktualitätsprüfung vollständig deaktiviert. Stripe generiert den Zeitstempel und die Signatur jedes Mal, wenn wir ein Ereignis an Ihren Endpoint senden. Wenn Stripe ein Ereignis wiederholt (zum Beispiel weil Ihr Endpoint zuvor mit einem Nicht-`2xx`-Statuscode geantwortet hat), generieren wir eine neue Signatur und einen neuen Zeitstempel für den neuen Zustellungsversuch. ### Schnelle Rückgabe einer 2xx-Antwort Ihr [Endpoint](https://docs.stripe.com/webhooks.md#example-endpoint) muss schnell einen erfolgreichen Statuscode (`2xx`) zurückgeben, bevor eine komplexe Logik angewendet wird, die eine Zeitüberschreitung verursachen könnte. Beispielsweise müssen Sie eine `200`-Antwort zurückgeben, bevor Sie eine Kundenrechnung in Ihrem Buchhaltungssystem als bezahlt aktualisieren können. ## See also - [Ereignisse an Amazon EventBridge senden](https://docs.stripe.com/event-destinations/eventbridge.md) - [Liste der Thin-Ereignistypen](https://docs.stripe.com/api/v2/events/event-types.md) - [Liste der Snapshot-Ereignistypen](https://docs.stripe.com/api/events/.md) - [Interaktiver Generator für Webhook-Endpoints](https://docs.stripe.com/webhooks/quickstart.md)