Triggering actions with webhooks

    How to use webhooks to respond to offline payment events.

    A webhook is an HTTP endpoint that receives events from Stripe.

    Webhooks allow you to be notified about payment events that happen in the real world outside of your payment flow such as:

    • Successful payments (payment_intent.succeeded)
    • Disputed payments (charge.dispute.created)
    • Available balance in your Stripe account (balance.available)

    While the Dashboard is great to use for one-off actions like refunding a payment or updating a customer’s information, webhooks are essential when it comes to scaling your payments integration by helping you process large volumes of business-critical events.

    Build your own webhook

    You can build a webhook handler on your own server to manage all your offline payment flows. Start by exposing an endpoint that can receive requests from Stripe and use the CLI to locally test your integration. Each request from Stripe contains an Event object with a reference to the object on Stripe that was modified.

    1 Create a webhook endpoint

    Add a new endpoint in your application. You can act on certain events by checking the type field of the event object sent in the request body. For now, let’s print to standard output to make sure our webhook is working.

    Start your server after adding the new endpoint.

    # Set your secret key: remember to change this to your live secret key in production # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' require 'stripe' require 'sinatra' require 'json' # Using the Sinatra framework set :port, 4242 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 # 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 # Unexpected event type status 400 return end status 200 end
    # Set your secret key: remember to change this to your live secret key in production # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' import json from django.http import HttpResponse # Using Django @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) # 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: # Unexpected event type return HttpResponse(status=400) return HttpResponse(status=200)
    // Set your secret key: remember to change this to your live secret key in production // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $payload = @file_get_contents('php://input'); $event = null; try { $event = StripeEvent::constructFrom( json_decode($payload, true) ); } catch(\UnexpectedValueException $e) { // Invalid payload http_response_code(400); exit(); } // Handle the event switch ($event->type) { case 'payment_intent.succeeded': $paymentIntent = $event->data->object; // contains a StripePaymentIntent handlePaymentIntentSucceeded($paymentIntent); break; case 'payment_method.attached': $paymentMethod = $event->data->object; // contains a StripePaymentMethod handlePaymentMethodAttached($paymentMethod); break; // ... handle other event types default: // Unexpected event type http_response_code(400); exit(); } http_response_code(200);
    // Set your secret key: remember to change this to your live secret key in production // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; import com.stripe.Stripe; import com.stripe.model.StripeObject; import com.stripe.net.ApiResource; import com.stripe.model.Event; import com.stripe.model.EventDataObjectDeserializer; import com.stripe.model.PaymentIntent; // Using the Spark framework (http://sparkjava.com) 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 ""; } // 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: // Unexpected event type response.status(400); return ""; } response.status(200); return ""; }
    // Set your secret key: remember to change this to your live secret key in production // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); // This example uses Express to receive webhooks const app = require('express')(); // Use body-parser to retrieve the raw body as a buffer const bodyParser = require('body-parser'); // Match the raw body to content type application/json app.post('/webhook', bodyParser.raw({type: 'application/json'}), (request, response) => { let event; try { event = JSON.parse(request.body); } 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: // Unexpected event type return response.status(400).end(); } // Return a 200 response to acknowledge receipt of the event response.json({received: true}); }); app.listen(4242, () => console.log('Running on port 4242'));
    // Set your secret key: remember to change this to your live secret key in production // See your keys here: https://dashboard.stripe.com/account/apikeys stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" 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, &e); 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 } 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, "Unexpected event type: %s\n", event.Type) w.WriteHeader(http.StatusBadRequest) return } w.WriteHeader(http.StatusOK) })
    // Set your secret key: remember to change this to your live secret key in production // See your keys here: https://dashboard.stripe.com/account/apikeys StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; using System; using System.IO; 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 Dashboard const string endpointSecret = "whsec_..."; [HttpPost] public async Task<IActionResult> Index() { var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); try { var stripeEvent = EventUtility.ParseEvent(json); // Handle the event if (stripeEvent.Type == Events.PaymentIntentSucceeded) { var paymentIntent = stripeEvent.Data.Object as PaymentIntent; Console.WriteLine("PaymentIntent was successful!"); } else if (stripeEvent.Type == Events.PaymentMethodAttached) { var paymentMethod = stripeEvent.Data.Object as PaymentMethod; Console.WriteLine("PaymentMethod was attached to a Customer!"); } // ... handle other event types else { // Unexpected event type return BadRequest(); } return Ok(); } catch (StripeException e) { return BadRequest(); } } } }

    2 Install and set up the Stripe CLI

    To install the Stripe CLI with homebrew, run:

    brew install stripe/stripe-cli/stripe

    To install the Stripe CLI on Debian and Ubuntu-based distributions:

    1. Add Bintray’s GPG key to the apt sources keyring:
    sudo apt-key adv --keyserver hkp://pool.sks-keyservers.net:80 --recv-keys 379CE192D401AB61
    1. Add stripe-cli’s apt repository to the apt sources list:
    echo "deb https://dl.bintray.com/stripe/stripe-cli-deb stable main" | sudo tee -a /etc/apt/sources.list
    1. Update the package list:
    sudo apt-get update
    1. Install the CLI:
    sudo apt-get install stripe

    Install the Stripe CLI on RedHat and CentOS-based distributions:

    1. Add stripe-cli’s yum repository to the yum sources list:
    wget https://bintray.com/stripe/stripe-cli-rpm/rpm -O bintray-stripe-stripe-cli-rpm.repo && sudo mv bintray-stripe-stripe-cli-rpm.repo /etc/yum.repos.d/
    1. Update the package list:
    sudo yum update
    1. Install the CLI:
    sudo yum install stripe

    To install the Stripe CLI with Scoop, run:

    scoop bucket add stripe https://github.com/stripe/scoop-stripe-cli.git
    scoop install stripe

    To install the Stripe CLI without homebrew:

    1. Download the latest mac-os tar.gz file from https://github.com/stripe/stripe-cli/releases/latest
    2. Unzip the file: tar -xvf stripe_X.X.X_mac-os_x86_64.tar.gz

    Optionally, install the binary in a location where you can execute it globally (e.g., ~/usr/local/bin).

    To install the Stripe CLI without a package manager:

    1. Download the latest linux tar.gz file from https://github.com/stripe/stripe-cli/releases/latest
    2. Unzip the file: tar -xvf stripe_X.X.X_linux_x86_64.tar.gz
    3. Run the executable: ./stripe

    To install the Stripe CLI without Scoop, run:

    1. Download the latest windows tar.gz file from https://github.com/stripe/stripe-cli/releases/latest
    2. Unzip the stripe_X.X.X_windows_x86_64.tar.gz file
    3. Run the unzipped .exe file

    The Stripe CLI is also available as a Docker image. Run the following command to install the latest version:

    docker run --rm -it stripe/stripe-cli latest

    Once you have the Stripe CLI installed, run stripe login in the command line to generate a pairing code to link to your Stripe account. Press Enter to launch your browser and log in to your Stripe account to allow access.

    stripe login Your pairing code is: humour-nifty-finer-magic Press Enter to open up the browser (^C to quit)

    The generated API key is valid for 90 days. You can modify or delete the key under API Keys in the Dashboard.

    If you want to use an existing API key, use the --api-key flag:

    stripe login --api-key `{{TEST_API_KEY}}` Your pairing code is: humour-nifty-finer-magic Press Enter to open up the browser (^C to quit)

    3 Test your webhook locally

    Use the CLI to forward events to your local webhook endpoint using the listen command.

    Assuming your application is running on port 4242, run:

    stripe listen --forward-to http://localhost:4242/webhook

    In a different terminal tab, use the trigger CLI command to trigger a mock webhook event.

    stripe trigger payment_intent.succeeded

    You should see the follow event in your listen tab:

    [200 POST] OK payment_intent.succeeded

    You should also see “PaymentIntent was successful!” printed in the terminal tab your server is running.

    Optional Check webhook signature

    Stripe includes a signature in each event’s Stripe-Signature header. This allows you to verify that the events were sent by Stripe, not by a third party. You can verify signatures either using our official libraries, or manually using your own solution.

    First, find your webhook endpoint secret and add it to your webhook handler as endpoint_secret. Since we are still using the Stripe CLI to develop our endpoint locally, use the trigger command to get the webhook endpoint secret from the CLI.

    stripe listen

    The webhook endpoint secret starts with whsec_ followed by a series of numbers and letters. Keep this webhook endpoint secret safe and never expose it publicly.

    # Set your secret key: remember to change this to your live secret key in production # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' require 'stripe' require 'sinatra' # If you are testing your webhook locally with the Stripe CLI you # can find the endpoint's secret by running `stripe listen` # Otherwise, find your endpoint's secret in your webhook settings in the Developer Dashboard endpoint_secret = 'whsec_...' # Using the Sinatra framework set :port, 4242 post '/my/webhook/url' do payload = request.body.read sig_header = request.env['HTTP_STRIPE_SIGNATURE'] event = nil begin event = Stripe::Webhook.construct_event( payload, sig_header, endpoint_secret ) rescue JSON::ParserError => e # Invalid payload status 400 return rescue Stripe::SignatureVerificationError => e # Invalid signature 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 # Unexpected event type status 400 return end status 200 end
    # Set your secret key: remember to change this to your live secret key in production # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' 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 Dashboard endpoint_secret = 'whsec_...' # Using Django @csrf_exempt def my_webhook_view(request): payload = request.body sig_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 return HttpResponse(status=400) except stripe.error.SignatureVerificationError as e: # Invalid signature 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: # Unexpected event type return HttpResponse(status=400) return HttpResponse(status=200)
    // Set your secret key: remember to change this to your live secret key in production // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); // 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); exit(); } catch(\Stripe\Exception\SignatureVerificationException $e) { // Invalid signature http_response_code(400); exit(); } // Handle the event switch ($event->type) { case 'payment_intent.succeeded': $paymentIntent = $event->data->object; // contains a StripePaymentIntent handlePaymentIntentSucceeded($paymentIntent); break; case 'payment_method.attached': $paymentMethod = $event->data->object; // contains a StripePaymentMethod handlePaymentMethodAttached($paymentMethod); break; // ... handle other event types default: // Unexpected event type http_response_code(400); exit(); } http_response_code(200);
    // Set your secret key: remember to change this to your live secret key in production // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; import com.stripe.Stripe; import com.stripe.model.StripeObject; import com.stripe.net.ApiResource; import com.stripe.model.Event; import com.stripe.model.EventDataObjectDeserializer; import com.stripe.model.PaymentIntent; // 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 String endpointSecret = "whsec_..."; // Using the Spark framework (http://sparkjava.com) 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 response.status(400); return ""; } catch (SignatureVerificationException e) { // Invalid 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; 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: // Unexpected event type response.status(400); return ""; } response.status(200); return ""; }
    // Set your secret key: remember to change this to your live secret key in production // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); // 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 const endpointSecret = 'whsec_...'; // This example uses Express to receive webhooks const app = require('express')(); // Use body-parser to retrieve the raw body as a buffer const bodyParser = require('body-parser'); // Match the raw body to content type application/json app.post('/webhook', bodyParser.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: // Unexpected event type return response.status(400).end(); } // Return a response to acknowledge receipt of the event response.json({received: true}); }); app.listen(4242, () => console.log('Running on port 4242'));
    // Set your secret key: remember to change this to your live secret key in production // See your keys here: https://dashboard.stripe.com/account/apikeys stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" 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 Dashboard endpointSecret := "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, "Unexpected event type: %s\n", event.Type) w.WriteHeader(http.StatusBadRequest) return } w.WriteHeader(http.StatusOK) })
    // Set your secret key: remember to change this to your live secret key in production // See your keys here: https://dashboard.stripe.com/account/apikeys StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; using System; using System.IO; 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 Dashboard const string endpointSecret = "whsec_..."; [HttpPost] public async Task<IActionResult> 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 (stripeEvent.Type == Events.PaymentIntentSucceeded) { var paymentIntent = stripeEvent.Data.Object as PaymentIntent; Console.WriteLine("PaymentIntent was successful!"); } else if (stripeEvent.Type == Events.PaymentMethodAttached) { var paymentMethod = stripeEvent.Data.Object as PaymentMethod; Console.WriteLine("PaymentMethod was attached to a Customer!"); } // ... handle other event types else { // Unexpected event type return BadRequest(); } return Ok(); } catch (StripeException e) { return BadRequest(); } } } }

    4 Deploy your webhook endpoint

    When you are ready to deploy your webhook endpoint to production there are a couple things you need to do:

    1. Use your live mode API keys and not your test mode keys.

    2. Configure your webhook endpoint in the Stripe Dashboard or with the API.

    To configure your endpoint in the Dashboard, go to your webhook settings.

    Click “Add endpoint” and enter the URL of your endpoint, the Stripe API version, and the specific events you want Stripe to send.

    Replace the webhook endpoint secret in your application with the new secret shown in the Dashboard view of your endpoint.

    That’s it! Your application is ready to accept live events. For more information on configuring your webhook endpoint, the Webhook Endpoint API, and testing in test mode see our Development guide.

    Was this page helpful?

    Thank you for helping improve Stripe's documentation. If you need help or have any questions, please consider contacting support.

    On this page