Skip to content
Create account
or
Sign in
The Stripe Docs logo
/
Ask AI
Create account
Sign in
Get started
Payments
Revenue
Platforms and marketplaces
Money management
Developer resources
Overview
About Stripe payments
Upgrade your integration
Payments analytics
Online payments
OverviewFind your use caseManaged Payments
Use Payment Links
Build a checkout page
Build an advanced integration
Build an in-app integration
Payment methods
Add payment methods
Manage payment methods
Faster checkout with Link
    Overview
    Instant Bank Payments
    Klarna on Link
    At a glance
    Link with Checkout
    Link with Web Elements
    Link with Mobile Elements
    Link with Invoicing
    Integration guides
    Link payment integrations
    Build a custom checkout page that includes Link
    Set up future payments using Elements and Link
Payment interfaces
Payment Links
Checkout
Web Elements
In-app Elements
Payment scenarios
Handle multiple currencies
Custom payment flows
Flexible acquiring
Orchestration
In-person payments
Terminal
Beyond payments
Incorporate your company
Crypto
Financial Connections
Climate
Understand fraud
Radar fraud protection
Manage disputes
Verify identities
HomePaymentsFaster checkout with Link

Build a custom checkout page that includes Link

Integrate Link using the Payment Element or Link Authentication Element.

This guide walks you through how to accept payments with Link using the Payment Intents API and either the Payment Element or Link Authentication Element.

There are three ways you can secure a customer email address for Link authentication and enrollment:

  • Pass in an email address: You can pass an email address to the Payment Element using defaultValues. If you’re already collecting the email address and or customer’s phone number in the checkout flow, we recommend this approach.
  • Collect an email address: You can collect an email address directly in the Payment Element. If you’re not collecting the email address anywhere in the checkout flow, we recommend this approach.
  • Link Authentication Element: You can use the Link Authentication Element to create a single email input field for both email collection and Link authentication. We recommend doing this if you use the Address Element.
Authenticate or enroll with Link directly in the Payment Element during checkout

Collect a customer email address for Link authentication or enrollment

Set up Stripe
Server-side

First, create a Stripe account or sign in.

Use our official libraries to access the Stripe API from your application:

Command Line
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
# Available as a gem sudo gem install stripe
Gemfile
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
# If you use bundler, you can add this line to your Gemfile gem 'stripe'

Create a PaymentIntent
Server-side

Stripe uses a PaymentIntent object to represent your intent to collect payment from a customer, tracking charge attempts and payment state changes throughout the process.

An overview diagram of the entire payment flow

If you collect card details for future usage with Setup Intents, list payment methods manually instead of using dynamic payment methods. To use Link without dynamic payment methods, update your integration to pass link to payment_method_types.

When you create a PaymentIntent, dynamically offer your customers the most relevant payment methods, including Link, by using dynamic payment methods. To use dynamic payment methods, don’t include the payment_method_types parameter. Optionally, you can also enable automatic_payment_methods.

Note

When your integration doesn’t set the payment_method_types parameter, some payment methods turn on automatically, including cards and wallets.

To add Link to your Elements integration using dynamic payment methods:

  1. In your Dashboard payment method settings, turn on Link.
  2. If you have an existing integration that manually lists payment methods, remove the payment_method_types parameter from your integration.

Retrieve the client secret

The PaymentIntent includes a client secret that the client side uses to securely complete the payment process. You can use different approaches to pass the client secret to the client side.

Retrieve the client secret from an endpoint on your server, using the browser’s fetch function. This approach is best if your client side is a single-page application, particularly one built with a modern frontend framework like React. Create the server endpoint that serves the client secret:

main.rb
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
get '/secret' do intent = # ... Create or retrieve the PaymentIntent {client_secret: intent.client_secret}.to_json end

And then fetch the client secret with JavaScript on the client side:

(async () => { const response = await fetch('/secret'); const {client_secret: clientSecret} = await response.json(); // Render the form using the clientSecret })();

Collect customer email

Link authenticates a customer by using their email address. Depending on your checkout flow, you have the following options: pass an email to the Payment Element, collect it directly within the Payment Element, or use the Link Authentication Element. Of these, Stripe recommends passing a customer email address to the Payment Element if available.

If any of the following apply to you:

  • You want a single, optimized component for email collection and Link authentication.
  • You need to collect a shipping address from your customer.

Then use the integration flow that implements these elements: the Link Authentication Element, Payment Element and optional Address Element.

A Link-enabled checkout page has the Link Authentication Element at the beginning, followed by the Address Element, and the Payment Element at the end. You can also display the Link Authentication Element on separate pages, in this same order, for multi-page checkout flows.

Create a payment form using multiple Elements

Create a payment form using multiple Elements

The integration works as follows:

Set up your payment form
Client-side

Now you can set up your custom payment form with the Elements prebuilt UI components. Your payment page address must start with https:// rather than http:// for your integration to work. You can test your integration without using HTTPS. Enable HTTPS when you’re ready to accept live payments.

The Link Authentication Element renders an email address input. When Link matches a customer email with an existing Link account, it sends the customer a secure, one-time code to their phone to authenticate. If the customer successfully authenticates, Stripe displays their Link-saved addresses and payment methods automatically for them to use.

This integration also creates the Payment Element, which renders a dynamic form that allows your customer to pick a payment method type. The form automatically collects all necessary payments details for the payment method type selected by the customer. The Payment Element also handles the display of Link-saved payment methods for authenticated customers.

Set up Stripe Elements

Install React Stripe.js and the Stripe.js loader from the npm public registry.

Command Line
npm install --save @stripe/react-stripe-js @stripe/stripe-js

On your payment page, wrap your payment form with the Elements component, passing the client secret from the previous step. ​​If you already collect the customer’s email in another part of your form, replace your existing input with the linkAuthenticationElement​.

If you don’t collect email, add the linkAuthenticationElement​ to your checkout flow. You must place the linkAuthenticationElement before the ShippingAddressElement (optional if you collect shipping addresses) and the PaymentElement for Link to autofill Link-saved details for your customer in the ShippingAddressElement and PaymentElement. You can also pass in the appearance option, customizing the Elements to match the design of your site.

If you have the customer’s email, pass it to the defaultValues option of the linkAuthenticationElement. This prefills their email address and initiates the Link authentication process.

If you have other customer information, pass it to the defaultValues.billingDetails object for the PaymentElement. Prefilling as much information as possible simplifies Link account creation and reuse for your customers.

Then, render the linkAuthenticationElement and PaymentElement components in your payment form:

Checkout.jsx
import {loadStripe} from "@stripe/stripe-js"; import { Elements, LinkAuthenticationElement, PaymentElement, } from "@stripe/react-stripe-js"; const stripe = loadStripe(
'pk_test_TYooMQauvdEDq54NiTphI7jx'
); // Customize the appearance of Elements using the Appearance API. const appearance = {/* ... */}; // Enable the skeleton loader UI for the optimal loading experience. const loader = 'auto';

The linkAuthenticationElement, PaymentElement, and ShippingAddressElement don’t need to be on the same page. If you have a process where customer contact information, shipping details, and payment details display to the customer in separate steps, you can display each Element in the appropriate step or page. Include the linkAuthenticationElement as the email input form in the contact info collection step to make sure the customer can take full advantage of the shipping and payment autofill provided by Link.

If you collect your customer’s email with the Link Authentication Element early in the checkout flow, you don’t need to show it again on the shipping or payment pages.

Retrieve an email address

You can retrieve the email address details using the onChange prop on the linkAuthenticationElement component. The onChange handler fires whenever the user updates the email field, or when a saved customer email is autofilled.

<linkAuthenticationElement onChange={(event) => { setEmail(event.value.email); }} />

Prefill a customer email address

The Link Authentication Element accepts an email address. Providing a customer’s email address triggers the Link authentication flow as soon as the customer lands on the payment page using the defaultValues option.

<linkAuthenticationElement options={{defaultValues: {email: 'foo@bar.com'}}}/>

OptionalPrefill additional customer data
Client-side

If you have it, prefilling customer information further streamlines the checkout process and reduces manual data entry.

If you’re using the Link Authentication Element, add the defaultValues.billingDetails object to the Payment Element to prefill a customer’s name and phone number as well as their shipping addresses. By prefilling as much of your customer’s information as possible, you simplify Link account creation and reuse.

Prefilled information in Link opt in form.

Prefill your customer’s email address, phone number, and name to simplify the Link sign-up process

You can provide the following values to the defaultValues.billingDetails object:

ValueRequiredFormat
nameOptionalstring
phoneOptionalstring
addressOptionalJSON object with the fields postal_code, and country. All fields are strings.

A Payment Element with all of its values prefilled looks similar to the following examples:

<PaymentElement options={{ defaultValues: { billingDetails: { name: 'John Doe', phone: '888-888-8888', address: { postal_code: '10001', country: 'US', } }, }, }} />;

OptionalCollect shipping addresses
Client-side

To collect addresses, create an empty DOM node for the Address Element to render into. The Address Element must be displayed after the Link Authentication Element for Link to autofill a customer’s saved address details:

Checkout.jsx
import {loadStripe} from "@stripe/stripe-js"; import { Elements, LinkAuthenticationElement, AddressElement, PaymentElement, } from "@stripe/react-stripe-js"; const stripe = loadStripe(
'pk_test_TYooMQauvdEDq54NiTphI7jx'
); // Customize the appearance of Elements using the Appearance API. const appearance = {/* ... */}; // Enable the skeleton loader UI for the optimal loading experience. const loader = 'auto'; const CheckoutPage = ({clientSecret}) => ( <Elements stripe={stripe} options={{clientSecret, appearance, loader}}> <CheckoutForm /> </Elements> ); export default function CheckoutForm() { return ( <form> <h3>Contact info</h3> <LinkAuthenticationElement /> <h3>Shipping</h3> <AddressElement options={{mode: 'shipping', allowedCountries: ['US']}} /> <h3>Payment</h3> <PaymentElement /> <button type="submit">Submit</button> </form> ); }

Display the AddressElement before the PaymentElement. The PaymentElement dynamically detects address data collected by the AddressElement, hiding unnecessary fields and collecting additional billing address details as necessary.

Retrieve address information

The AddressElement automatically passes the shipping address when a customer submits the payment, but you can also retrieve the address details on the frontend using the onChange property. The onChange handler sends an event whenever the user updates any field in the Address Element or selects a saved address:

<AddressElement onChange={(event) => { setAddressState(event.value); }} />

Prefill a shipping address

Use defaultValues to prefill address information, speeding checkout for your customers.

<AddressElement options={{ mode: 'shipping', defaultValues: { name: 'Jane Doe', address: { line1: '354 Oyster Point Blvd', line2: '', city: 'South San Francisco', state: 'CA', postal_code: '94080', country: 'US', } } }}>

OptionalCustomize the appearance
Client-side

After you add these Elements to your page, you can customize their appearance to make them fit with the rest of your design:

Customize the appearance of your Elements

Customize the appearance of your Elements

Submit the payment to Stripe
Client-side

Use stripe.confirmPayment to complete the payment with details collected from your customer in the different Elements forms. Provide a return_url to this function to indicate where Stripe redirects the user after they complete the payment.

Your user might be first redirected to an intermediate site, like a bank authorization page, before Stripe redirects them to the return_url.

By default, card and bank payments immediately redirect to the return_url when a payment is successful. If you don’t want to redirect to the return_url, you can use if_required to change the behavior.

Checkout.jsx
import {loadStripe} from "@stripe/stripe-js"; import { useStripe, useElements, Elements, LinkAuthenticationElement, PaymentElement, // If collecting shipping AddressElement, } from "@stripe/react-stripe-js"; const stripe = loadStripe(
'pk_test_TYooMQauvdEDq54NiTphI7jx'
); const appearance = {/* ... */}; // Enable the skeleton loader UI for the optimal loading experience. const loader = 'auto'; const CheckoutPage =({clientSecret}) => ( <Elements stripe={stripe} options={{clientSecret, appearance, loader}}> <CheckoutForm /> </Elements> ); export default function CheckoutForm() { const stripe = useStripe(); const elements = useElements(); const handleSubmit = async (event) => { event.preventDefault(); const {error} = await stripe.confirmPayment({ elements, confirmParams: { return_url: "https://example.com/order/123/complete", }, }); if (error) { // handle error } }; return ( <form onSubmit={handleSubmit}> <h3>Contact info</h3> <LinkAuthenticationElement /> {/* If collecting shipping */} <h3>Shipping</h3> <AddressElement options={{mode: 'shipping', allowedCountries: ['US']}} /> <h3>Payment</h3> <PaymentElement /> <button type="submit">Submit</button> </form> ); }

The return_url corresponds to a page on your website that provides the payment status of the PaymentIntent when you render the return page. When Stripe redirects the customer to the return_url, you can use the following URL query parameters to verify payment status. You can also append your own query parameters when providing the return_url. These query parameters persist through the redirect process.

ParameterDescription
payment_intentThe unique identifier for the PaymentIntent
payment_intent_client_secretThe client secret of the PaymentIntent object.

OptionalSeparate authorization and capture
Server-side

Link supports separate authorization and capture. You must capture an authorized Link payment within 7 days of the authorization. Otherwise, the authorization is automatically canceled and you can’t capture that payment.

Tell Stripe to authorize only

To indicate that you want separate authorization and capture, set capture_method to manual when creating the PaymentIntent. This parameter instructs Stripe to only authorize the amount on the customer’s payment method.

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/payment_intents \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d "payment_method_types[]"=link \ -d "payment_method_types[]"=card \ -d amount=1099 \ -d currency=usd \ -d capture_method=manual

Capture the funds

After the authorization succeeds, the PaymentIntent status transitions to requires_capture. To capture the authorized funds, make a PaymentIntent capture request. The total authorized amount is captured by default—you can’t capture more than this, but you can capture less.

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/payment_intents/
{{PAYMENT_INTENT_ID}}
/capture
\ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d amount_to_capture=750

Optional Cancel the authorization

If you need to cancel an authorization, you can cancel the PaymentIntent.

Handle post-payment events
Server-side

Stripe sends a payment_intent.succeeded event when the payment completes. Use a webhook to receive these events and run actions, like sending an order confirmation email to your customer, logging the sale in a database, or starting a shipping workflow.

Configure your integration to listen for these events rather than waiting on a callback from the client. When you wait on a callback from the client, the customer can close the browser window or quit the app before the callback executes. Setting up your integration to listen for asynchronous events enables you to accept different types of payment methods with a single integration.

In addition to handling the payment_intent.succeeded event, you can also handle two other important events when collecting payments with the Payment Element:

EventDescriptionAction
payment_intent.succeededSent from Stripe when a customer has successfully completed a payment.Send the customer an order confirmation and fulfill their order.
payment_intent.payment_failedSent from Stripe when a customer attempted a payment, but the payment didn’t succeed.If a payment transitioned from processing to payment_failed, offer the customer another attempt to pay.

Test the integration

Caution

Don’t store real user data in sandbox Link accounts. Treat them as if they’re publicly available, because these test accounts are associated with your publishable key.

Currently, Link only works with credit cards, debit cards, and qualified US bank account purchases. Link requires domain registration.

You can create sandbox accounts for Link using any valid email address. The following table shows the fixed one-time passcode values that Stripe accepts for authenticating sandbox accounts:

ValueOutcome
Any other 6 digits not listed belowSuccess
000001Error, code invalid
000002Error, code expired
000003Error, max attempts exceeded

For testing specific payment methods, refer to the Payment Element testing examples.

Multiple funding sources

As Stripe adds additional funding source support, you don’t need to update your integration. Stripe automatically supports them with the same transaction settlement time and guarantees as card and bank account payments.

Card authentication and 3D Secure

Link supports 3D Secure 2 (3DS2) authentication for card payments. 3DS2 requires customers to complete an additional verification step with the card issuer when paying. Payments that have been successfully authenticated using 3D Secure are covered by a liability shift.

To trigger 3DS2 authentication challenge flows with Link in a sandbox, use the following test card with any CVC, postal code, and future expiration date: .

In a sandbox, the authentication process displays a mock authentication page. On that page, you can either authorize or cancel the payment. Authorizing the payment simulates successful authentication and redirects you to the specified return URL. Clicking the Failure button simulates an unsuccessful attempt at authentication.

For more details, refer to the 3D Secure authentication page.

Note

When testing 3DS flows, only test cards for 3DS2 will trigger authentication on Link.

OptionalDisplay customer-saved data
Server-side
Client-side

In addition to displaying your own saved addresses and payment methods for a Customer, you can display their Link-saved data.

If a customer has more than one saved payment method, Stripe displays the three most recently used cards saved to the Customer in addition to any payment methods the customer has saved with Link.

Preview of customer saved data

To accomplish this, create an Ephemeral Key and send it to your frontend along with the Customer ID. Information on the customer object is sensitive—you can’t retrieve it directly within Stripe.js. An Ephemeral Key grants temporary access to customer data.

Command Line
curl
Ruby
Python
PHP
Node.js
No results
curl https://api.stripe.com/v1/ephemeral_keys \ -u
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:
\ -H "Stripe-Version: 2025-08-27.basil" \ -d "customer"="{{CUSTOMER_ID}}" \ curl https://api.stripe.com/v1/payment_intents \ -u
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:
\ -d "amount"=1099 \ -d "currency"="usd" \ -d "customer"="{{CUSTOMER_ID}}" \ -d "payment_method_types[]"="link" \ -d "payment_method_types[]"="card"

On the client-side, fetch the customerOptions with clientSecret.

(async () => { const response = await fetch('/secret'); const {clientSecret, customerOptions} = await response.json(); })

Then, pass the customerOptions.ephemeralKey and customerOptions.customer values to the customerOptions option on the Elements group. You must also pass the elements_customers_beta_1 beta flag when loading the Stripe instance.

Checkout.jsx
import {loadStripe} from "@stripe/stripe-js"; import { useStripe, useElements, Elements, LinkAuthenticationElement, PaymentElement, } from "@stripe/react-stripe-js"; const stripe = loadStripe(
'pk_test_TYooMQauvdEDq54NiTphI7jx'
, { apiVersion: '2025-08-27.basil', betas: ['elements_customers_beta_1'], }); const appearance = {/* ... */}; const loader = 'auto'; const CheckoutPage =({ clientSecret, customerOptions, }) => ( <Elements stripe={stripe} options={{ clientSecret, appearance, loader, customerOptions, }}> <CheckoutForm /> </Elements> );

OptionalSave Link payment methods
Server-side
Client-side

You can save Link payment methods for future off-session payments or subscriptions, but not for future on-session payments. To do so, you must attach it to a Customer. Create a customer object when your customer creates an account with your business. Then, specify the customer when creating your PaymentIntent.

When a new customer has their first transaction with your business, create a customer object in Stripe to store their data for future use.

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/payment_intents \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d amount=1099 \ -d currency=usd \ -d "automatic_payment_methods[enabled]"=true \ -d customer=
{{CUSTOMER_ID}}
\ -d setup_future_usage=off_session

In the latest version of the API, specifying the automatic_payment_methods parameter is optional because Stripe enables its functionality by default.

When you’re ready to charge your customer again, use the customer and resulting PaymentMethod ID to create a new PaymentIntent. Set off_session to true. This causes the PaymentIntent to send an error if authentication is required when your customer isn’t actively using your site or app.

Command Line
curl
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/payment_intents \ -u
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:
\ -d amount=1099 \ -d currency=usd \ # In the latest version of the API, specifying the `automatic_payment_methods` parameter is optional because Stripe enables its functionality by default. -d "automatic_payment_methods[enabled]"=true \ -d customer="{{CUSTOMER_ID}}" \ -d payment_method="{{PAYMENT_METHOD_ID}}" \ -d return_url="https://example.com/order/123/complete" \ -d off_session=true \ -d confirm=true

Disclose Stripe to your customers

Stripe collects information on customer interactions with Elements to provide services to you, prevent fraud, and improve its services. This includes using cookies and IP addresses to identify which Elements a customer saw during a single checkout session. You’re responsible for disclosing and obtaining all rights and consents necessary for Stripe to use data in these ways. For more information, visit our privacy center.

See also

  • What is Link
  • Link with Elements
  • Link in the Payment Element
  • Explore the Link Authentication Element
  • Link in different payment integrations
Was this page helpful?
YesNo
  • Need help? Contact Support.
  • Join our early access program.
  • Check out our changelog.
  • Questions? Contact Sales.
  • LLM? Read llms.txt.
  • Powered by Markdoc