{"html":"\u003Cheader\u003E\n \u003Ch1 id=\"checkout-purchase-fulfillment\"\u003ECheckout Purchase Fulfillment\u003C/h1\u003E\n\n \u003Cp\u003ELearn how to fulfill purchases with Checkout.\u003C/p\u003E\n\u003C/header\u003E\n\n\u003Csection\u003E\n \u003Cp\u003EWhen your customer successfully completes their payment or initiates a\nsubscription using \u003Ca href=\"/docs/payments/checkout\"\u003ECheckout\u003C/a\u003E, Stripe redirects them to\nthe URL that you specified in the \u003Ccode\u003Esuccess_url\u003C/code\u003E parameter (or \u003Ccode\u003EsuccessUrl\u003C/code\u003E in\nthe client integration). Typically, this is a page on your website that informs\nyour customer that their payment was successful.\u003C/p\u003E\n\n \u003Cp\u003EAt a later point, you may want to fulfill the customer’s purchase and deliver\nthe goods or services they paid for. There are a few ways to do this:\u003C/p\u003E\n\n \u003Cul\u003E\n \u003Cli\u003E\u003Ca href=\"#dashboard\"\u003EManually, with the Dashboard\u003C/a\u003E\u003C/li\u003E\n \u003Cli\u003E\u003Ca href=\"#plugins\"\u003EAutomatically, using third-party plugins\u003C/a\u003E\u003C/li\u003E\n \u003Cli\u003E\u003Ca href=\"#webhooks\"\u003EAutomatically, using webhooks\u003C/a\u003E\u003C/li\u003E\n \u003Cli\u003E\u003Ca href=\"#polling\"\u003EAutomatically, by periodically polling for successful purchases\u003C/a\u003E\u003C/li\u003E\n \u003C/ul\u003E\n\n \u003Caside class=\"important amber\"\u003E\n \u003Cp\u003EDo not rely on the redirect to the \u003Ccode\u003Esuccess_url\u003C/code\u003E alone for fulfilling purchases as:\u003C/p\u003E\n\n \u003Cul\u003E\n \u003Cli\u003EMalicious users could directly access the \u003Ccode\u003Esuccess_url\u003C/code\u003E without paying and\ngain access to your goods or services.\u003C/li\u003E\n \u003Cli\u003ECustomers may not always reach the \u003Ccode\u003Esuccess_url\u003C/code\u003E after a successful payment.\nIt is possible they close their browser tab before the redirect occurs.\u003C/li\u003E\n \u003C/ul\u003E\n \u003C/aside\u003E\n\u003C/section\u003E\n\n\u003Csection\u003E\n \u003Ch2 id=\"dashboard\"\u003EFulfilling purchases with the Dashboard\u003C/h2\u003E\n\n \u003Cp\u003EWhen a customer successfully completes a payment, it is recorded as a\nnew entry in the Dashboard’s \u003Ca href=\"https://dashboard.stripe.com/payments\"\u003Elist of payments\u003C/a\u003E. When\nyou click an entry in the list, it takes you to the payment detail page. In\nthe \u003Cstrong\u003ECheckout summary\u003C/strong\u003E section, you can find the customer’s billing\ninformation and the list of items that they purchased. You can use this\ninformation to manually fulfill the customer’s purchase.\u003C/p\u003E\n\n \u003Cp\u003E\u003Ca href=\"/img/docs/checkout/source.png\" class=\"zoomable\"\u003E\n \u003Cimg src=\"/img/docs/checkout/source.png\" width=\"100%\" /\u003E\n\u003C/a\u003E\u003C/p\u003E\n\n \u003Cp\u003EDashboard fulfillment is only practical for digital goods that you can send to the\ncustomer via e-mail. If you intend to fulfill purchases that include physical\ngoods, your application should collect shipping details.\u003C/p\u003E\n\n \u003Caside class=\"important neutral\"\u003E\n \u003Cp\u003EWhen a customer successfully pays for a recurring service, they are automatically\nsubscribed to the plan you specified. Their subscription is recorded as a new\nentry in the Dashboard’s \u003Ca href=\"https://dashboard.stripe.com/subscriptions\"\u003Elist of subscriptions\u003C/a\u003E.\u003C/p\u003E\n \u003C/aside\u003E\n\n \u003Caside class=\"important neutral\"\u003E\n \u003Cp\u003EStripe can help you keep up with incoming payments by sending you email\nnotifications whenever a customer successfully completes a payment. Use the\nDashboard to \u003Ca href=\"https://dashboard.stripe.com/settings/user\"\u003Econfigure email\nnotifications\u003C/a\u003E.\u003C/p\u003E\n \u003C/aside\u003E\n\u003C/section\u003E\n\n\u003Csection\u003E\n \u003Ch2 id=\"plugins\"\u003EFulfilling purchases with third-party plugins\u003C/h2\u003E\n \u003Cp\u003EYou can use plugins like \u003Ca href=\"https://stripe.com/works-with/zapier\"\u003EZapier\u003C/a\u003E to\nautomate the process of updating your purchase fulfillment systems with\ninformation from Stripe payments.\u003C/p\u003E\n\n \u003Cp\u003EExamples of automations supported by plugins include:\u003C/p\u003E\n\n \u003Cul\u003E\n \u003Cli\u003EUpdating spreadsheets used for order tracking in response to successful\npayments\u003C/li\u003E\n \u003Cli\u003EUpdating inventory management systems in response to successful payments\u003C/li\u003E\n \u003Cli\u003ETriggering notifications to internal customer service teams via email or\nchat applications\u003C/li\u003E\n \u003C/ul\u003E\n\u003C/section\u003E\n\n\u003Csection\u003E\n \u003Ch2 id=\"webhooks\"\u003EFulfilling purchases with webhooks\u003C/h2\u003E\n\n \u003Cp\u003EStripe can send \u003Ca href=\"/docs/webhooks\"\u003Ewebhook\u003C/a\u003E events to your server to notify you\nwhen a customer completes their payment. You can create a handler for the event\nand use it to execute any code that you need to fulfill the customer’s purchase.\nTo handle a webhook event, create an HTTP endpoint on your server and configure\nthe webhook endpoint \u003Ca href=\"https://dashboard.stripe.com/account/webhooks\"\u003Ein the Dashboard\u003C/a\u003E.\u003C/p\u003E\n\n \u003Cp\u003EStripe sends the \u003Ccode\u003Echeckout.session.completed\u003C/code\u003E event when a Checkout payment is\nsuccessful. The webhook payload includes the \u003Ca href=\"/docs/api/checkout/sessions\"\u003ECheckout Session\nobject\u003C/a\u003E, which contains information about the \u003Cspan class=\"tooltip-wrapper\"\u003E\u003Cspan class=\"tooltip-content\"\u003E\u003Ca href=\"/docs/api/customers\"\u003ECustomer\u003C/a\u003E\u003C/span\u003E\u003Ctemplate class=\"tooltip\"\u003E\u003Cspan class=\"definition-tooltip key-popover\"\u003E\u003Cspan class=\"key-popover-tick\"\u003E\u003C/span\u003EStripe \u003Cem\u003ECustomer\u003C/em\u003E objects allow you to perform recurring charges, and to track multiple charges, that are associated with the same customer.\u003C/span\u003E\u003C/template\u003E\u003C/span\u003E, \u003Cspan class=\"tooltip-wrapper\"\u003E\u003Cspan class=\"tooltip-content\"\u003E\u003Ca href=\"/docs/payments/payment-intents\"\u003EPaymentIntent\u003C/a\u003E\u003C/span\u003E\u003Ctemplate class=\"tooltip\"\u003E\u003Cspan class=\"definition-tooltip key-popover\"\u003E\u003Cspan class=\"key-popover-tick\"\u003E\u003C/span\u003EThe \u003Cem\u003EPayment Intents API\u003C/em\u003E is a new way to build dynamic payment flows. It tracks the lifecycle of a customer checkout flow and triggers additional authentication steps when required by regulatory mandates, custom Radar fraud rules, or redirect-based payment methods.\u003C/span\u003E\u003C/template\u003E\u003C/span\u003E, or\n\u003Cspan class=\"tooltip-wrapper\"\u003E\u003Cspan class=\"tooltip-content\"\u003E\u003Ca href=\"/docs/billing/subscriptions/creating\"\u003ESubscription\u003C/a\u003E\u003C/span\u003E\u003Ctemplate class=\"tooltip\"\u003E\u003Cspan class=\"definition-tooltip key-popover\"\u003E\u003Cspan class=\"key-popover-tick\"\u003E\u003C/span\u003EA \u003Cem\u003ESubscription\u003C/em\u003E represents Product details associated with a Plan, specific to one of your Customers.\u003C/span\u003E\u003C/template\u003E\u003C/span\u003E, and\noptionally the \u003Ccode\u003Eclient_reference_id\u003C/code\u003E if you provided it when calling\n\u003Ccode\u003EredirectToCheckout\u003C/code\u003E on the client.\u003C/p\u003E\n\n \u003Cp\u003ELearn more about \u003Ca href=\"/docs/webhooks/setup\"\u003Esetting up webhooks\u003C/a\u003E.\u003C/p\u003E\n\n \u003Cdiv class=\"tabs tabs-code animated\"\u003E\n \u003Cnav class=\"nav-languages\"\u003E\n \u003Ca data-language=\"ruby\"\n \u003ERuby\u003C/a\u003E\n \u003Ca data-language=\"python\"\n \u003EPython\u003C/a\u003E\n \u003Ca data-language=\"php\"\n \u003EPHP\u003C/a\u003E\n \u003Ca data-language=\"java\"\n \u003EJava\u003C/a\u003E\n \u003Ca data-language=\"node\"\n \u003ENode\u003C/a\u003E\n \u003Ca data-language=\"go\"\n \u003EGo\u003C/a\u003E\n \u003Ca data-language=\"dotnet\"\n \u003E.NET\u003C/a\u003E\n \u003C/nav\u003E\n \u003Cdiv class=\"tabs-content\"\u003E\n \u003Cdiv class=\"code\" data-language=\"ruby\"\u003E\n \u003Cpre class=\"language-ruby numbered\" \u003E\u003Ccode\u003E# Set your secret key: remember to change this to your live secret key in production\n# See your keys here: https://dashboard.stripe.com/account/apikeys\nStripe.api_key = \u0026#39;sk_test_4eC39HqLyjWDarjtT1zdp7dc\u0026#39;\n\n# You can find your endpoint\u0026#39;s secret in your webhook settings\nendpoint_secret = \u0026#39;whsec_...\u0026#39;\n\n# Using Sinatra\npost \u0026#39;/webhook\u0026#39; do\n payload = request.body.read\n event = nil\n\n # Verify webhook signature and extract the event\n # See https://stripe.com/docs/webhooks/signatures for more information.\n sig_header = request.env[\u0026#39;HTTP_STRIPE_SIGNATURE\u0026#39;]\n begin\n event = Stripe::Webhook.construct_event(\n payload, sig_header, endpoint_secret\n )\n rescue JSON::ParserError =\u0026gt; e\n # Invalid payload\n status 400\n return\n rescue Stripe::SignatureVerificationError =\u0026gt; e\n # Invalid signature\n status 400\n return\n end\n\n # Handle the checkout.session.completed event\n if event[\u0026#39;type\u0026#39;] == \u0026#39;checkout.session.completed\u0026#39;\n session = event[\u0026#39;data\u0026#39;][\u0026#39;object\u0026#39;]\n\n # Fulfill the purchase...\n handle_checkout_session(session)\n end\n\n status 200\nend\n\u003C/code\u003E\u003C/pre\u003E\n\n\u003C/div\u003E\n\n\u003Cdiv class=\"code\" data-language=\"python\"\u003E\n \u003Cpre class=\"language-python numbered\" \u003E\u003Ccode\u003E# Set your secret key: remember to change this to your live secret key in production\n# See your keys here: https://dashboard.stripe.com/account/apikeys\nstripe.api_key = \u0026#39;sk_test_4eC39HqLyjWDarjtT1zdp7dc\u0026#39;\n\n# Using Django\nfrom django.http import HttpResponse\n\n# You can find your endpoint\u0026#39;s secret in your webhook settings\nendpoint_secret = \u0026#39;whsec_...\u0026#39;\n\n@csrf_exempt\ndef my_webhook_view(request):\n payload = request.body\n sig_header = request.META[\u0026#39;HTTP_STRIPE_SIGNATURE\u0026#39;]\n event = None\n\n try:\n event = stripe.Webhook.construct_event(\n payload, sig_header, endpoint_secret\n )\n except ValueError as e:\n # Invalid payload\n return HttpResponse(status=400)\n except stripe.error.SignatureVerificationError as e:\n # Invalid signature\n return HttpResponse(status=400)\n\n # Handle the checkout.session.completed event\n if event[\u0026#39;type\u0026#39;] == \u0026#39;checkout.session.completed\u0026#39;:\n session = event[\u0026#39;data\u0026#39;][\u0026#39;object\u0026#39;]\n\n # Fulfill the purchase...\n handle_checkout_session(session)\n\n return HttpResponse(status=200)\n\u003C/code\u003E\u003C/pre\u003E\n\n\u003C/div\u003E\n\n\u003Cdiv class=\"code\" data-language=\"php\"\u003E\n \u003Cpre class=\"language-php numbered\" \u003E\u003Ccode\u003E// Set your secret key: remember to change this to your live secret key in production\n// See your keys here: https://dashboard.stripe.com/account/apikeys\n\\Stripe\\Stripe::setApiKey(\u0026#39;sk_test_4eC39HqLyjWDarjtT1zdp7dc\u0026#39;);\n\n// You can find your endpoint\u0026#39;s secret in your webhook settings\n$endpoint_secret = \u0026#39;whsec_...\u0026#39;;\n\n$payload = @file_get_contents(\u0026#39;php://input\u0026#39;);\n$sig_header = $_SERVER[\u0026#39;HTTP_STRIPE_SIGNATURE\u0026#39;];\n$event = null;\n\ntry {\n $event = \\Stripe\\Webhook::constructEvent(\n $payload, $sig_header, $endpoint_secret\n );\n} catch(\\UnexpectedValueException $e) {\n // Invalid payload\n http_response_code(400); // PHP 5.4 or greater\n exit();\n} catch(\\Stripe\\Error\\SignatureVerification $e) {\n // Invalid signature\n http_response_code(400); // PHP 5.4 or greater\n exit();\n}\n\n// Handle the checkout.session.completed event\nif ($event-\u0026gt;type == \u0026#39;checkout.session.completed\u0026#39;) {\n $session = $event-\u0026gt;data-\u0026gt;object;\n\n // Fulfill the purchase...\n handle_checkout_session($session);\n}\n\nhttp_response_code(200); // PHP 5.4 or greater\n\u003C/code\u003E\u003C/pre\u003E\n\n\u003C/div\u003E\n\n\u003Cdiv class=\"code\" data-language=\"java\"\u003E\n \u003Cpre class=\"language-java numbered\" \u003E\u003Ccode\u003E// Set your secret key: remember to change this to your live secret key in production\n// See your keys here: https://dashboard.stripe.com/account/apikeys\nStripe.apiKey = \u0026quot;sk_test_4eC39HqLyjWDarjtT1zdp7dc\u0026quot;;\n\n// You can find your endpoint\u0026#39;s secret in your webhook settings\nString endpointSecret = \u0026quot;whsec_...\u0026quot;;\n\n// Using the Spark framework (http://sparkjava.com)\npublic Object handle(Request request, Response response) {\n String payload = request.body();\n String sigHeader = request.headers(\u0026quot;Stripe-Signature\u0026quot;);\n Event event = null;\n\n try {\n event = Webhook.constructEvent(\n payload, sigHeader, endpointSecret\n );\n } catch (JsonSyntaxException e) {\n // Invalid payload\n response.status(400);\n return \u0026quot;\u0026quot;;\n } catch (SignatureVerificationException e) {\n // Invalid signature\n response.status(400);\n return \u0026quot;\u0026quot;;\n }\n\n // Handle the checkout.session.completed event\n if (\u0026quot;checkout.session.completed\u0026quot;.equals(event.getType())) {\n Session session = (Session) event.getDataObjectDeserializer().getObject();\n\n // Fulfill the purchase...\n handleCheckoutSession(session);\n }\n\n response.status(200);\n return \u0026quot;\u0026quot;;\n}\n\u003C/code\u003E\u003C/pre\u003E\n\n\u003C/div\u003E\n\n\u003Cdiv class=\"code\" data-language=\"node\"\u003E\n \u003Cpre class=\"language-javascript numbered\" \u003E\u003Ccode\u003E// Set your secret key: remember to change this to your live secret key in production\n// See your keys here: https://dashboard.stripe.com/account/apikeys\nconst stripe = require(\u0026#39;stripe\u0026#39;)(\u0026#39;sk_test_4eC39HqLyjWDarjtT1zdp7dc\u0026#39;);\n\n// Find your endpoint\u0026#39;s secret in your Dashboard\u0026#39;s webhook settings\nconst endpointSecret = \u0026#39;whsec_...\u0026#39;;\n\n// Using Express\nconst app = require(\u0026#39;express\u0026#39;)();\n\n// Use body-parser to retrieve the raw body as a buffer\nconst bodyParser = require(\u0026#39;body-parser\u0026#39;);\n\n// Match the raw body to content type application/json\napp.post(\u0026#39;/webhook\u0026#39;, bodyParser.raw({type: \u0026#39;application/json\u0026#39;}), (request, response) =\u0026gt; {\n const sig = request.headers[\u0026#39;stripe-signature\u0026#39;];\n\n let event;\n\n try {\n event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret);\n } catch (err) {\n return response.status(400).send(`Webhook Error: ${err.message}`);\n }\n\n // Handle the checkout.session.completed event\n if (event.type === \u0026#39;checkout.session.completed\u0026#39;) {\n const session = event.data.object;\n\n // Fulfill the purchase...\n handleCheckoutSession(session);\n }\n\n // Return a response to acknowledge receipt of the event\n response.json({received: true});\n});\n\napp.listen(8000, () =\u0026gt; console.log(\u0026#39;Running on port 8000\u0026#39;));\n\u003C/code\u003E\u003C/pre\u003E\n\n\u003C/div\u003E\n\n\u003Cdiv class=\"code\" data-language=\"go\"\u003E\n \u003Cpre class=\"language-go numbered\" \u003E\u003Ccode\u003E// Set your secret key: remember to change this to your live secret key in production\n// See your keys here: https://dashboard.stripe.com/account/apikeys\nstripe.Key = \u0026quot;sk_test_4eC39HqLyjWDarjtT1zdp7dc\u0026quot;\n\nhttp.HandleFunc(\u0026quot;/webhook\u0026quot;, func(w http.ResponseWriter, req *http.Request) {\n body, err := ioutil.ReadAll(req.Body)\n if err != nil {\n fmt.Fprintf(os.Stderr, \u0026quot;Error reading request body: %v\\n\u0026quot;, err)\n w.WriteHeader(http.StatusServiceUnavailable)\n return\n }\n\n // Pass the request body \u0026amp; Stripe-Signature header to ConstructEvent, along with the webhook signing key\n // You can find your endpoint\u0026#39;s secret in your webhook settings\n endpointSecret := \u0026quot;whsec_...\u0026quot;;\n event, err := webhook.ConstructEvent(body, req.Header.Get(\u0026quot;Stripe-Signature\u0026quot;), endpointSecret)\n\n if err != nil {\n fmt.Fprintf(os.Stderr, \u0026quot;Error verifying webhook signature: %v\\n\u0026quot;, err)\n w.WriteHeader(http.StatusBadRequest) // Return a 400 error on a bad signature\n return\n }\n\n // Handle the checkout.session.completed event\n if event.Type == \u0026quot;checkout.session.completed\u0026quot; {\n var session stripe.CheckoutSession\n err := json.Unmarshal(event.Data.Raw, \u0026amp;session)\n if err != nil {\n fmt.Fprintf(os.Stderr, \u0026quot;Error parsing webhook JSON: %v\\n\u0026quot;, err)\n w.WriteHeader(http.StatusBadRequest)\n return\n }\n\n // Fulfill the purchase...\n handleCheckoutSession(session)\n }\n\n w.WriteHeader(http.StatusOK)\n})\n\u003C/code\u003E\u003C/pre\u003E\n\n\u003C/div\u003E\n\n\u003Cdiv class=\"code\" data-language=\"dotnet\"\u003E\n \u003Cpre class=\"language-dotnet numbered\" \u003E\u003Ccode\u003E// Set your secret key: remember to change this to your live secret key in production\n// See your keys here: https://dashboard.stripe.com/account/apikeys\nStripeConfiguration.SetApiKey(\u0026quot;sk_test_4eC39HqLyjWDarjtT1zdp7dc\u0026quot;);\n\nusing System;\nusing System.IO;\nusing Microsoft.AspNetCore.Mvc;\nusing Stripe;\n\nnamespace workspace.Controllers\n{\n [Route(\u0026quot;api/[controller]\u0026quot;)]\n public class StripeWebHook : Controller\n {\n // You can find your endpoint\u0026#39;s secret in your webhook settings\n const string secret = \u0026quot;whsec_...\u0026quot;;\n\n [HttpPost]\n public void Index()\n {\n var json = new StreamReader(HttpContext.Request.Body).ReadToEnd();\n\n try\n {\n var stripeEvent = EventUtility.ConstructEvent(json,\n Request.Headers[\u0026quot;Stripe-Signature\u0026quot;], secret);\n\n // Handle the checkout.session.completed event\n if (stripeEvent.Type == Events.CheckoutSessionCompleted)\n {\n var session = stripeEvent.Data.Object as CheckoutSession;\n\n // Fulfill the purchase...\n HandleCheckoutSession(session);\n }\n\n }\n catch (StripeException e)\n {\n return BadRequest();\n }\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\n\n\u003C/div\u003E\n\n \u003C/div\u003E\n \u003C/div\u003E\n\n\n \u003Caside class=\"important neutral\"\u003E\n \u003Cp\u003EStripe may send \u003Ccode\u003Echeckout.session.completed\u003C/code\u003E events before or after your\ncustomer is redirected to the \u003Ccode\u003Esuccess_url\u003C/code\u003E.\u003C/p\u003E\n \u003C/aside\u003E\n\n \u003Cp\u003EYou can obtain information about the customer, payment, or subscription by\nretrieving the Customer, PaymentIntent, or Subscription objects referenced by\nthe \u003Ccode\u003Ecustomer\u003C/code\u003E, \u003Ccode\u003Epayment_intent\u003C/code\u003E, and \u003Ccode\u003Esubscription\u003C/code\u003E properties in the webhook\npayload.\u003C/p\u003E\n\u003C/section\u003E\n\n\u003Csection\u003E\n \u003Ch2 id=\"polling\"\u003EFulfilling purchases by polling for events\u003C/h2\u003E\n \u003Cp\u003EYou can periodically check for new payments made via Checkout by polling the\n\u003Ca href=\"/docs/api/events/list\"\u003E\u003Ccode\u003E/v1/events\u003C/code\u003E\u003C/a\u003E endpoint for new\n\u003Ccode\u003Echeckout.session.completed\u003C/code\u003E events. You can then execute any code that you need\nto fulfill the customer’s purchase when you detect a new\n\u003Ccode\u003Echeckout.session.completed\u003C/code\u003E event.\u003C/p\u003E\n\n \u003Cp\u003EStripe generates the \u003Ccode\u003Echeckout.session.completed\u003C/code\u003E event when a Checkout payment\nis successful. The event payload includes the \u003Ca href=\"/docs/api/checkout/sessions/object\"\u003ECheckout Session\nobject\u003C/a\u003E, which contains information about\nthe \u003Cspan class=\"tooltip-wrapper\"\u003E\u003Cspan class=\"tooltip-content\"\u003E\u003Ca href=\"/docs/api/customers\"\u003ECustomer\u003C/a\u003E\u003C/span\u003E\u003Ctemplate class=\"tooltip\"\u003E\u003Cspan class=\"definition-tooltip key-popover\"\u003E\u003Cspan class=\"key-popover-tick\"\u003E\u003C/span\u003EStripe \u003Cem\u003ECustomer\u003C/em\u003E objects allow you to perform recurring charges, and to track multiple charges, that are associated with the same customer.\u003C/span\u003E\u003C/template\u003E\u003C/span\u003E, \u003Cspan class=\"tooltip-wrapper\"\u003E\u003Cspan class=\"tooltip-content\"\u003E\u003Ca href=\"/docs/payments/payment-intents\"\u003EPaymentIntent\u003C/a\u003E\u003C/span\u003E\u003Ctemplate class=\"tooltip\"\u003E\u003Cspan class=\"definition-tooltip key-popover\"\u003E\u003Cspan class=\"key-popover-tick\"\u003E\u003C/span\u003EThe \u003Cem\u003EPayment Intents API\u003C/em\u003E is a new way to build dynamic payment flows. It tracks the lifecycle of a customer checkout flow and triggers additional authentication steps when required by regulatory mandates, custom Radar fraud rules, or redirect-based payment methods.\u003C/span\u003E\u003C/template\u003E\u003C/span\u003E, or\n\u003Cspan class=\"tooltip-wrapper\"\u003E\u003Cspan class=\"tooltip-content\"\u003E\u003Ca href=\"/docs/billing/subscriptions/creating\"\u003ESubscription\u003C/a\u003E\u003C/span\u003E\u003Ctemplate class=\"tooltip\"\u003E\u003Cspan class=\"definition-tooltip key-popover\"\u003E\u003Cspan class=\"key-popover-tick\"\u003E\u003C/span\u003EA \u003Cem\u003ESubscription\u003C/em\u003E represents Product details associated with a Plan, specific to one of your Customers.\u003C/span\u003E\u003C/template\u003E\u003C/span\u003E, and\noptionally the \u003Ccode\u003Eclient_reference_id\u003C/code\u003E if you provided it when calling\n\u003Ccode\u003EredirectToCheckout\u003C/code\u003E on the client.\u003C/p\u003E\n\n \u003Cp\u003EThe following example demonstrates how to retrieve all\n\u003Ccode\u003Echeckout.session.completed\u003C/code\u003E events in the last 24 hours:\u003C/p\u003E\n\n \u003Cdiv class=\"tabs tabs-code animated\"\u003E\n \u003Cnav class=\"nav-languages\"\u003E\n \u003Ca data-language=\"ruby\"\n \u003ERuby\u003C/a\u003E\n \u003Ca data-language=\"python\"\n \u003EPython\u003C/a\u003E\n \u003Ca data-language=\"php\"\n \u003EPHP\u003C/a\u003E\n \u003Ca data-language=\"java\"\n \u003EJava\u003C/a\u003E\n \u003Ca data-language=\"node\"\n \u003ENode\u003C/a\u003E\n \u003Ca data-language=\"go\"\n \u003EGo\u003C/a\u003E\n \u003Ca data-language=\"dotnet\"\n \u003E.NET\u003C/a\u003E\n \u003C/nav\u003E\n \u003Cdiv class=\"tabs-content\"\u003E\n \u003Cdiv class=\"code\" data-language=\"ruby\"\u003E\n \u003Cpre class=\"language-ruby numbered\" \u003E\u003Ccode\u003E# Set your secret key: remember to change this to your live secret key in production\n# See your keys here: https://dashboard.stripe.com/account/apikeys\nStripe.api_key = \u0026#39;sk_test_4eC39HqLyjWDarjtT1zdp7dc\u0026#39;\n\nevents = Stripe::Event.list(type: \u0026#39;checkout.session.completed\u0026#39;, created: {\n # Check for events created in the last 24 hours.\n gte: Time.now.utc.to_i - 24 * 60 * 60,\n})\n\nevents.auto_paging_each do |event|\n session = event[\u0026#39;data\u0026#39;][\u0026#39;object\u0026#39;]\n\n # Fulfill the purchase...\n handle_checkout_session(session)\nend\n\u003C/code\u003E\u003C/pre\u003E\n\n\u003C/div\u003E\n\n\u003Cdiv class=\"code\" data-language=\"python\"\u003E\n \u003Cpre class=\"language-python numbered\" \u003E\u003Ccode\u003E# Set your secret key: remember to change this to your live secret key in production\n# See your keys here: https://dashboard.stripe.com/account/apikeys\nstripe.api_key = \u0026#39;sk_test_4eC39HqLyjWDarjtT1zdp7dc\u0026#39;\n\nevents = stripe.Event.list(type = \u0026#39;checkout.session.completed\u0026#39;, created = {\n # Check for events created in the last 24 hours.\n \u0026#39;gte\u0026#39;: int(time.time() - 24 * 60 * 60),\n})\n\nfor event in events.auto_paging_iter():\n session = event[\u0026#39;data\u0026#39;][\u0026#39;object\u0026#39;]\n\n # Fulfill the purchase...\n handle_checkout_session(session)\n\u003C/code\u003E\u003C/pre\u003E\n\n\u003C/div\u003E\n\n\u003Cdiv class=\"code\" data-language=\"php\"\u003E\n \u003Cpre class=\"language-php numbered\" \u003E\u003Ccode\u003E// Set your secret key: remember to change this to your live secret key in production\n// See your keys here: https://dashboard.stripe.com/account/apikeys\n\\Stripe\\Stripe::setApiKey(\u0026#39;sk_test_4eC39HqLyjWDarjtT1zdp7dc\u0026#39;);\n\n$events = \\Stripe\\Event::all([\n \u0026#39;type\u0026#39; =\u0026gt; \u0026#39;checkout.session.completed\u0026#39;,\n \u0026#39;created\u0026#39; =\u0026gt; [\n // Check for events created in the last 24 hours.\n \u0026#39;gte\u0026#39; =\u0026gt; time() - 24 * 60 * 60,\n ],\n]);\n\nforeach ($events-\u0026gt;autoPagingIterator() as $event) {\n $session = $event-\u0026gt;data-\u0026gt;object;\n\n // Fulfill the purchase...\n handle_checkout_session($session);\n}\n\u003C/code\u003E\u003C/pre\u003E\n\n\u003C/div\u003E\n\n\u003Cdiv class=\"code\" data-language=\"java\"\u003E\n \u003Cpre class=\"language-java numbered\" \u003E\u003Ccode\u003E// Set your secret key: remember to change this to your live secret key in production\n// See your keys here: https://dashboard.stripe.com/account/apikeys\nStripe.apiKey = \u0026quot;sk_test_4eC39HqLyjWDarjtT1zdp7dc\u0026quot;;\n\nHashMap\u0026lt;String, Object\u0026gt; params = new HashMap\u0026lt;\u0026gt;();\nparams.put(\u0026quot;type\u0026quot;, \u0026quot;checkout.session.completed\u0026quot;);\nHashMap\u0026lt;String, Object\u0026gt; createdParams = new HashMap\u0026lt;\u0026gt;();\n// Check for events created in the last 24 hours.\ncreatedParams.put(\u0026quot;gte\u0026quot;, (int) ((System.currentTimeMillis() / 1000) - 24 * 60 * 60));\nIterable\u0026lt;Event\u0026gt; events = Event.list(params).autoPagingIterable();\n\nfor (Event event : events) {\n EventDataObjectDeserializer deserializer = event.getDataObjectDeserializer();\n if (deserializer.getObject().isPresent()) {\n Session session = (Session) deserializer.getObject().get();\n\n // Fulfill the purchase...\n handleCheckoutSession(session);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\n\n\u003C/div\u003E\n\n\u003Cdiv class=\"code\" data-language=\"node\"\u003E\n \u003Cpre class=\"language-javascript numbered\" \u003E\u003Ccode\u003E// Set your secret key: remember to change this to your live secret key in production\n// See your keys here: https://dashboard.stripe.com/account/apikeys\nconst stripe = require(\u0026#39;stripe\u0026#39;)(\u0026#39;sk_test_4eC39HqLyjWDarjtT1zdp7dc\u0026#39;);\n\n(async () =\u0026gt; {\n const events = stripe.events.list({\n type: \u0026#39;checkout.session.completed\u0026#39;,\n created: {\n // Check for events created in the last 24 hours.\n gte: Date.now() - 24 * 60 * 60,\n },\n });\n\n // For older versions of Node, see https://github.com/stripe/stripe-node/#auto-pagination\n for await (const event of events) {\n const session = event.data.object;\n\n // Fulfill the purchase...\n handleCheckoutSession(session);\n }\n})();\n\u003C/code\u003E\u003C/pre\u003E\n\n\u003C/div\u003E\n\n\u003Cdiv class=\"code\" data-language=\"go\"\u003E\n \u003Cpre class=\"language-go numbered\" \u003E\u003Ccode\u003E// Set your secret key: remember to change this to your live secret key in production\n// See your keys here: https://dashboard.stripe.com/account/apikeys\nstripe.Key = \u0026quot;sk_test_4eC39HqLyjWDarjtT1zdp7dc\u0026quot;\n\nparams := \u0026amp;stripe.EventListParams{\n Type: stripe.String(\u0026quot;checkout.session.completed),\n CreatedRange: \u0026amp;stripe.RangeQueryParams{\n // Check for events created in the last 24 hours.\n GreaterThan: stripe.Int64(time.Now().Unix() - 24 * 60 * 60),\n },\n}\ni := event.List(params)\n\nfor i.Next() {\n event := i.Event()\n var session stripe.checkout.Session\n err := json.Unmarshal(event.Data.Raw, \u0026amp;session)\n if err == nil {\n // Fulfill the purchase...\n handleCheckoutSession(session)\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\n\n\u003C/div\u003E\n\n\u003Cdiv class=\"code\" data-language=\"dotnet\"\u003E\n \u003Cpre class=\"language-dotnet numbered\" \u003E\u003Ccode\u003E// Set your secret key: remember to change this to your live secret key in production\n// See your keys here: https://dashboard.stripe.com/account/apikeys\nStripeConfiguration.SetApiKey(\u0026quot;sk_test_4eC39HqLyjWDarjtT1zdp7dc\u0026quot;);\n\nvar service = new EventService();\nvar options = new EventListOptions {\n Type = \u0026quot;checkout.session.created\u0026quot;,\n CreatedRange = new DateRangeOptions{\n // Check for events created in the last 24 hours.\n GreaterThan = DateTime.Now.Subtract(new TimeSpan(24, 0, 0)),\n }\n}\n\nforeach (var stripeEvent in service.ListAutoPaging(options)) {\n var session = stripeEvent.Data.Object as Session;\n\n // Fulfill the purchase...\n handleCheckoutSession(session);\n}\n\u003C/code\u003E\u003C/pre\u003E\n\n\u003C/div\u003E\n\n\n \u003C/div\u003E\n \u003C/div\u003E\n\n\n \u003Caside class=\"important amber\"\u003E\n \u003Cp\u003EPolling the \u003Ccode\u003E/v1/events\u003C/code\u003E endpoint will return all events matching your query.\nYou are responsible for ensuring that you do not fulfill the same purchase\nmultiple times. Verify the uniqueness of events using the\n\u003Ca href=\"/docs/api/checkout/sessions/object#checkout_session_object-id\"\u003E\u003Ccode\u003Eid\u003C/code\u003E\u003C/a\u003E attribute\nof the Checkout Session object.\u003C/p\u003E\n \u003C/aside\u003E\n\n \u003Cp\u003EYou can obtain information about the customer, payment, or subscription by\nretrieving the Customer, PaymentIntent, or Subscription objects referenced by\nthe \u003Ccode\u003Ecustomer\u003C/code\u003E, \u003Ccode\u003Epayment_intent\u003C/code\u003E, and \u003Ccode\u003Esubscription\u003C/code\u003E properties in the webhook\npayload.\u003C/p\u003E\n\u003C/section\u003E\u003Cfooter\u003E\n\n\u003Ch2 data-no-anchor\u003EQuestions?\u003C/h2\u003E\n\n\u003Cp\u003E\n We're always happy to help with code or other questions you might have. \u003Ca href='#' id='search-link' class='search-link'\u003ESearch our documentation\u003C/a\u003E, \u003Ca href='https://support.stripe.com/contact'\u003Econtact support\u003C/a\u003E, or \u003Ca href='https://stripe.com/contact/sales'\u003Econnect with our sales team\u003C/a\u003E. You can also chat live with other developers in \u003Ca href=\"irc://irc.freenode.net/stripe\"\u003E#stripe\u003C/a\u003E on freenode.\n\u003C/p\u003E\n\u003Cscript nonce=\"RTCDvXfPhX6v7xeOQwFnTw==\"\u003E document.getElementById(\"search-link\").addEventListener(\"click\", focusSearch);\n function focusSearch() {\n document.querySelector(\".search input\").focus();\n window.scrollTo(0,0);\n }\n\u003C/script\u003E\n\u003Cp data-csat-step=\"poll\" class=\"csat-widget\"\u003E\n \u003Cspan class=\"csat-widget-text\"\u003EWas this page helpful?\u003C/span\u003E\n \u003Cspan data-csat-poll=\"yes\" class=\"csat-button csat-button-yes common-Button\"\u003EYes\u003C/span\u003E\n \u003Cspan data-csat-poll=\"no\" class=\"csat-button csat-button-no common-Button\"\u003ENo\u003C/span\u003E\n\u003C/p\u003E\n\n\u003Cdiv data-csat-step=\"input\" class=\"csat-widget\"\u003E\n \u003Cdiv class=\"csat-input\"\u003E\n \u003Cinput id=\"csat-input-text\" placeholder=\"Feedback about this page?\" class=\"common-Input\"\u003E\n \u003Cspan id=\"csat-input-submit\" class=\"csat-button common-Link\"\u003ESend\u003C/span\u003E\n \u003C/div\u003E\n \u003Cp class=\"csat-contact\"\u003E\n \u003Clabel class=\"csat-contact-option\"\u003E\n \u003Cinput id=\"csat-contact-optin-checkbox\" type=\"checkbox\"\u003E\n Yes, it’s okay to follow up by email.\n \u003C/label\u003E\n \u003C/p\u003E\n\u003C/div\u003E\n\n\u003Cp data-csat-step=\"reply\" class=\"csat-widget\"\u003E\n \u003Cspan class=\"csat-reply-main\"\u003EThank you for helping improve Stripe's documentation.\u003C/span\u003E\n \u003Cspan class=\"csat-reply-extended\"\u003EIf you need help or have any questions, please consider \u003Ca href=\"https://support.stripe.com/email\"\u003Econtacting support\u003C/a\u003E.\u003C/span\u003E\n\u003C/p\u003E\n\n\n\u003C/footer\u003E\n","head_css":"","documentation_footer_js":"","title":"Checkout Purchase Fulfillment | Stripe Checkout","translated":null}