Developer tools
Webhooks
Build webhooks

Build a webhook endpoint

Write the code that properly handles webhook notifications.

The first step to adding webhooks to your Stripe integration is to build your own custom endpoint. 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.

Before looking at the code, there are several key considerations regardless of the technology involved. You should also review the best practices for using webhooks.

Key considerations

For each event occurrence, Stripe POSTs the webhook data to your endpoint in JSON format. The full event details are included and can be used directly after parsing the JSON into an Event object. Thus, at minimum, the webhook endpoint needs to expect data through a POST request and confirm successful receipt of that data. Beyond those two concepts, you should also…

Return a 2xx status code quickly

To acknowledge receipt of an event, your endpoint must return a 2xx HTTP status code to Stripe. All response codes outside this range, including 3xx codes, indicate to Stripe that you did not receive the event.

If Stripe does not receive a 2xx HTTP status code, the notification attempt is repeated. After multiple failures to send the notification over multiple days, Stripe marks the event as failed and stops trying to send it to your endpoint. After multiple days without receiving any 2xx HTTP status code responses, Stripe emails you about the misconfigured endpoint, and automatically disables your endpoint soon after if unaddressed.

Because properly acknowledging receipt of the webhook notification is so important, your endpoint should return a 2xx HTTP status code prior to any complex logic could cause a timeout.

Test that your endpoint works

As your webhook endpoint is used asynchronously, its failures may not be obvious to you until it’s too late (e.g., after it’s been disabled). Always test that your endpoint works:

  • Upon initial creation
  • After taking it live
  • After making any changes

The test a webhook endpoint page explains how to test your endpoint, and the Stripe CLI makes testing simple.

Example code

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 # 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 # 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 # 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: # 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 // 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: // 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; // 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: // 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; // 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: // 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, &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, "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 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; // Then define and call a method to handle the successful payment intent. // handlePaymentIntentSucceeded(paymentIntent); } else if (stripeEvent.Type == Events.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 return BadRequest(); } return Ok(); } catch (StripeException e) { return BadRequest(); } } } }

Next steps

Was this page helpful?
Questions? Contact us.
Developer tutorials on YouTube.
You can unsubscribe at any time. Read our privacy policy.