Building a webhook endpoint

    Receive events on your local test environment to build your integration.

    With the Stripe CLI you can have events triggered on your Stripe account sent to your local environment and then forwarded on to your application. This helps you easily test a webhook integration without needing to deploy your application or figure out how to open up your local environment to the public internet in order for Stripe to communicate to it.

    Create a webhook endpoint

    Webhook data is sent as JSON in the POST request body. The full event details are included and can be used directly, after parsing the JSON into an Event object.

    Creating a webhook endpoint on your server is no different from creating any page on your website. With PHP, you might create a new .php file on your server; with a Ruby framework like Sinatra, you would add a new route with the desired URL.

    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
    
        # Handle the event
        case event.type
        when 'payment_intent.succeeded'
          payment_intent = event.data.object # contains a Stripe::PaymentIntent
          handle_payment_intent_succeeded(payment_intent)
        when 'payment_method.attached'
          payment_method = event.data.object # contains a Stripe::PaymentMethod
          handle_payment_method_attached(payment_method)
        # ... handle other event types
        else
          # Unexpected event type
          status 400
          return
        end
    
        status 200
    end
    
    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
        handle_payment_intent_succeeded(payment_intent)
      elif event.type == 'payment_method.attached':
        payment_method = event.data.object # contains a stripe.PaymentMethod
        handle_payment_method_attached(payment_method)
      # ... handle other event types
      else:
        # Unexpected event type
        return HttpResponse(status=400)
    
      return HttpResponse(status=200)
    
    $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();
    }
    
    // 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:
            // Unexpected event type
            http_response_code(400);
            exit();
    }
    
    http_response_code(200);
    
    // 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;
          handlePaymentIntentSucceeded(paymentIntent);
          break;
        case "payment_method.attached":
          PaymentMethod paymentMethod = (PaymentMethod) stripeObject;
          handlePaymentMethodAttached(paymentMethod);
          break;
        // ... handle other event types
        default:
          // Unexpected event type
          response.status(400);
          return "";
      }
    
      response.status(200);
      return "";
    }
    
    // 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;
          handlePaymentIntentSucceeded(paymentIntent);
          break;
        case 'payment_method.attached':
          const paymentMethod = event.data.object;
          handlePaymentMethodAttached(paymentMethod);
          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(8000, () => console.log('Running on port 8000'));
    
    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
            }
            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
            }
            handlePaymentMethodAttached(paymentMethod)
        // ... 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)
    })
    
    using System;
    using System.IO;
    using Microsoft.AspNetCore.Mvc;
    using Stripe;
    
    namespace workspace.Controllers
    {
        [Route("api/[controller]")]
        public class StripeWebHook : Controller
        {
            [HttpPost]
            public void Index()
            {
                var json = new StreamReader(HttpContext.Request.Body).ReadToEnd();
    
                try
                {
                    var stripeEvent = EventUtility.ParseEvent(json);
    
                    // Handle the event
                    if (stripeEvent.Type == Events.PaymentIntentSucceeded)
                    {
                        var paymentIntent = stripeEvent.Data.Object as PaymentIntent;
                        handlePaymentIntentSucceeded(paymentIntent);
                    }
                    else if (stripeEvent.Type == Events.PaymentMethodAttached)
                    {
                        var paymentMethod = stripeEvent.Data.Object as PaymentMethod;
                        handlePaymentMethodAttached(paymentMethod);
                    }
                    // ... handle other event types
                    else
                    {
                        // Unexpected event type
                        return BadRequest();
                    }
    
                }
                catch (StripeException e)
                {
                    return BadRequest();
                }
            }
        }
    }
    

    Listening and forwarding events

    The Stripe CLI provides a quick way for you to see events being created on your account. With the CLI you can get a look behind the scenes at all the events that will be sent to you:

    stripe listen
    Ready! Your webhook signing secret is {{SIGNING_SECRET}} (^C to quit)
    

    In a new tab, run:

    stripe trigger payment_intent.created
    

    Going back to the tab with listen running, you should see the event coming in:

    stripe listen
    Ready! Your webhook signing secret is {{SIGNING_SECRET}} (^C to quit)
    2019-09-24 11:01:22  Received: payment_intent.created [evt_1FMIKEByst5pquEtW2TQHNP6]
    

    Once you’ve seen events coming into the CLI you can start forwarding them to your application. Stop the listen command with Ctrl+C and start it again with the --forward-to flag:

    stripe listen --foward-to localhost:4242/webhook
    Ready! Your webhook signing secret is {{SIGNING_SECRET}} (^C to quit)
    

    Make sure to replace localhost:4242 with the address of your development server with the appropriate path!

    When forwarding events, the CLI takes the event created by Stripe and makes a request to the URL provided, simulating a webhook request from Stripe. In the CLI, you should see a 200 status code from your server after the request is made:

    stripe listen --foward-to localhost:4242/webhook
    Ready! Your webhook signing secret is {{SIGNING_SECRET}} (^C to quit)
    2019-09-24 11:34:54  Received: payment_intent.created [evt_1FMIqgByst5pquEtgAQ1Zreq]
    2019-09-24 11:34:54            [200] POST http://localhost:4242/webhook
    

    The listen command lets you filter events coming in to your CLI with the --events flag. This is useful if you only care about or want to test specific events. The flag accepts a comma-separated list of events:

    stripe listen --events payment_intent.created,payment_intent.completed \
      --forward-to localhost:4242/webhook
    Ready! Your webhook signing secret is {{SIGNING_SECRET}} (^C to quit)
    

    Triggering events

    The CLI ships a command called trigger that automatically creates certain event types for you. To trigger an event, run:

    stripe trigger <event_type> #OR
    stripe trigger payment_intent.created
    

    There are only a handful of events currently available, but the list is growing. To see supported trigger events, run stripe trigger --help or visit our GitHub page. If you’d like to see a new event added, open a feature request on GitHub.

    Testing new API versions

    The Stripe CLI lets you test upgrading to the latest Stripe API version without any side effects on your account. When invoking the listen command, give it the flag --latest to have events of the latest API version forwarded to you:

    stripe listen --latest --foward-to localhost:4242/webhook
    Ready! Your webhook signing secret is {{SIGNING_SECRET}} (^C to quit)
    

    Advanced usage

    Work with Connect

    In some cases, Connect events might be sent to a separate endpoint than standard account events. The CLI supports splitting these events using the --forward-connect-to flag, which operates similarly to the --forward-to flag.

    Set custom headers

    To add additional headers to the request the Stripe CLI makes, use the --headers (or --connect-headers when using `–forward-connect-to):

    $ stripe listen --forward-to localhost:4242/webhook --headers ...
    

    Use jq to parse events

    The CLI has the option to print events that come in as JSON instead of the default output. Provide the listen command with the --print-json flag to print the JSON to standard out:

    $ stripe listen --print-json
    

    Using the --print-json flag is useful if you want to work with a tool like jq to filter or manipulate the input. For example, you can display the type of all webhooks Stripe sends to you:

    $ stripe listen --print-json | jq .type
    

    Load your saved endpoints

    If you’ve already setup webhook endpoints in the Dashboard, the CLI has a flag to load and configure those endpoints for you as local forwarding. The flag is --load-from-webhooks-api and must be used in conjunction with the --forward-to flag:

    $ stripe listen --load-from-webhooks-api --forward-to localhost:4242
    

    The flag works by loading the endpoints defined in the Dashboard through our Wehooks API. It parses the path and event mapping for each defined endpoint, then appends that path to the --forward-to path provided while maintaining the event list for each endpoint.

    For example, let’s say you defined two endpoints on the Dashboard:

    1. https://kavholm.com/webhook/charges consumes charge.created and charge.failed
    2. https://kavholm.com/webhook/payment_intents consumes payment_intent.created

    When you invoke the CLI with --forward-to localhost:4242, the CLI inbounds events to two locations:

    1. http://localhost:4242/webhook/charges with charge.created and charge.failed
    2. http://localhost:4242/webhook/payment_intents with payment_intent.created

    Local HTTPS

    If you’re developing in a local environment that has self-signed HTTPS certificates, you can disable certificate verification with --skip-verify.

    Next steps

    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