Stripe Link

Accept one-click payments from shoppers who saved their details with Stripe Link.

View as Markdown

Stripe Link is Stripe’s accelerated-checkout network. A shopper saves their payment details with Link once, and on any later Link-enabled checkout Stripe recognizes them and lets them pay in one click.

Link appears to a shopper in one of two ways:

  • Express button — the shopper already has an authenticated Link session in their browser. A “Pay with Link” button renders alongside your Apple Pay and Google Pay buttons.
  • Tile — the shopper is not recognized. Link is offered as a selectable payment method, and choosing it launches Stripe’s Link sign-in.

Both paths mount the same Link element and complete through the same onLinkResult callback — the tile is a different placement, not a different flow.

Prerequisites

Link is Stripe-only. It cannot be fulfilled by Airwallex, Authorize.net, or any other processor, and a saved Link payment method cannot be re-routed to another processor.

Enable Link on your Stripe processor before integrating:

  1. Go to Orchestration > Payment processors and open your Stripe processor.
  2. Under Accept digital wallet payments, turn on Stripe Link.
  3. Click Save Processor.

The toggle appears only on Stripe processors. Processor configuration is dashboard-only — it is not part of the public API. Unlike Apple Pay and Google Pay, Link does not require registering a custom domain. See Stripe for the full processor setup.

Add Stripe.js to your checkout page:

1<script src="https://js.stripe.com/v3/"></script>

PaymentKit collects card details through VGS, not Stripe Elements. The Link element mounts into its own container beside your card form — the two coexist on the same page.

Setup

1<script src="https://unpkg.com/@payment-kit-js/vanilla/dist/cdn/paymentkit.min.js"></script>
2<script>
3 const paymentKit = PaymentKit.default({
4 environment: 'production',
5 secureToken: 'your_secure_token',
6 paymentMethods: [PaymentKit.PaymentMethods.stripeLink]
7 });
8</script>

Initialize Link

Unlike Apple Pay and Google Pay — where you render a button that calls paymentKit.submit()Link is the button. Stripe renders the express button and fires its own confirm event, so the integration is callback-driven.

Register your callbacks first, then call initStripeLink:

1paymentKit.stripe_link.onLinkResult((result) => {
2 if (result.errors) {
3 // Surface the error or fall back to the card form
4 return;
5 }
6 window.location.href = '/success';
7});
8
9paymentKit.stripe_link.onLinkAuthChange((showExpress) => {
10 expressContainer.hidden = !showExpress;
11 linkTile.hidden = showExpress;
12});
13
14await paymentKit.stripe_link.initStripeLink({
15 processorId: 'proc_prod_a1b2c3d4e5f6g7h8',
16 customerInfo: {
17 first_name: 'Jane',
18 last_name: 'Smith'
19 }
20});

initStripeLink fetches the checkout’s Link configuration, sets up the Link element, and starts silent session detection. Mounting the visible button is a separate step — see Mount the express button. If Link is not available for the checkout — no Stripe processor, Link disabled, or Stripe.js missing — it stays quiet and no button renders.

The ordering above matters: onLinkReady and onLinkAuthChange both fire before the returned promise resolves, and neither emission is replayed for a callback registered later.

paymentKit.submit() does not work for Link. Calling it with paymentMethod: 'stripe_link' returns a directive error pointing you back to initStripeLink() and mountLinkButton().

Mount the express button

Mount the button into an empty container that Link owns — the SDK appends its own child node inside the selector you pass, and only removes that node again on re-mount. Never point it at your VGS card fields.

Call this only after initStripeLink has resolved. Before that the express element does not exist yet and the call fails with Express element not set up.

1const { success, error } = paymentKit.stripe_link.mountLinkButton('#link-express-button');
2if (!success) {
3 console.error('[Link] mount failed:', error);
4}
1<div id="link-express-button"></div>

mountLinkButton reports failure through its return value rather than throwing, so check success. Mounting is idempotent — calling it again replaces the button rather than stacking a second one.

React to session state

Three callbacks cover the Link lifecycle — two drive what you render, one delivers the result. Register as many handlers per callback as you need; none are overwritten.

CallbackFires with
onLinkReadytrue once the express element is set up and ready to mount
onLinkAuthChangeshowExpresstrue to render the express button, false to fall back to the Link tile. Only meaningful once onLinkReady has fired
onLinkResultThe final result after the shopper confirms with Link

onLinkAuthChange reflects whether the shopper has an authenticated Link session. Whenever Link is advertised for the checkout, exactly one of the express button or the tile applies: the tile is the fallback when no session is recognized. If Link is not advertised at all, neither renders — showExpress is false and both containers should stay hidden.

Link uses its own isolated Stripe element, so it does not suppress your other express buttons. A recognized Link shopper on a wallet-capable device sees Link, Apple Pay, and Google Pay together.

Handle the result

onLinkResult receives either a data object or an errors object.

1paymentKit.stripe_link.onLinkResult((result) => {
2 if (result.errors) {
3 console.error(result.errors.stripe_link);
4 return;
5 }
6 console.log('Checkout Session:', result.data.checkoutSessionId);
7 console.log('Payment Intent:', result.data.paymentIntentId);
8 console.log('Payment Method:', result.data.paymentMethodId);
9});

Result fields

FieldDescription
idStripe payment intent ID for the charge. Absent in setup mode — no charge is created
checkoutSessionIdCheckout session ID. Falls back to the checkout’s secure token if the backend omits it
checkoutAttemptIdCheckout attempt ID
state"checkout_succeeded" on success
paymentIntentIdPaymentKit payment intent ID (e.g. pi_dev_def456) — not the Stripe payment intent
customerIdCustomer the payment was recorded against
paymentMethodIdSaved Link payment method ID
processorUsedProcessor that fulfilled the payment
subscriptionIdSubscription created, when applicable
invoiceId / invoiceNumberInvoice created, when applicable. invoiceNumber is an integer
cardBrand / cardLast4Both null for Link — a Link payment method carries no underlying card details. See Save Link for later

data also carries errorCode, errorMessageForCustomer, and errorMessageForDebug. These are only populated on a failure, which arrives via result.errors — on a success result they are undefined.

Payer identity

Link returns the shopper’s identity when they confirm. The SDK captures it and sends it with the confirmation — you do not pass anything or call any extra method.

CapturedNotes
EmailUsed for receipts, dunning, and deduplicating repeat purchases
NameThe single name Link returns is split on the first space — “Ada Byron King” becomes first name “Ada”, last name “Byron King”
Billing addressCollected by the Link element, then discarded server-side when every toggle under Settings > Checkout > Fields > Billing address is off
PhoneNot requested, but forwarded when Stripe volunteers it anyway

Captured values only fill fields that are blank. Anything you already supplied in customerInfo wins, so Link never overwrites merchant-supplied data. Because customerInfo.first_name and last_name are typed as required, pass empty strings when you want Link to supply the shopper’s name.

The Link element always asks for the billing address, even when you collect none. Stripe only surfaces the payer identity when it can fill both name and address, so declining the address would cost you the email too. Your field setting is honored on the server instead — the address is dropped at confirm time rather than never collected.

Phone is not requested. Any field Stripe cannot autofill from the shopper’s Link account is collected in the payment interface instead — an extra step at the highest-intent moment in the funnel.

Save Link for later

The Link payment method is always saved against the customer. What varies is whether it carries an off-session mandate — the thing that makes it chargeable later without the shopper present.

CheckoutChargeOff-session mandate
Setup modeNoneYes
Payment mode with recurring line itemsOne-time chargeYes
Payment mode, one-time items onlyOne-time chargeNo

The SDK reads this from the checkout — there is no separate call.

Saved Link methods read back through the normal payment-method endpoints and, with the mandate, can be charged off-session for renewals and future invoices.

A saved Link method renders as a Link mark, not a card number. Link abstracts the underlying instrument, so brand and last four come back null by design — build your UI to omit them rather than to display a card.

Clean up

Tear down the Link element, its observers, and all registered callbacks when unmounting or navigating away. The call is idempotent.

1paymentKit.stripe_link.teardownStripeLink();

In React, call this from your effect cleanup so re-mounts do not stack duplicate buttons. Teardown drops your callbacks too, so re-register them before calling initStripeLink again.

Options

OptionTypeDescription
processorIdstringRequired. Your Stripe processor ID with Link enabled
customerInfoStripeLinkCustomerInfoRequired. Only first_name, last_name, and email are accepted — other customer fields cannot be passed on the Link path
customerInfo.first_namestringRequired. Customer’s first name
customerInfo.last_namestringRequired. Customer’s last name
customerInfo.emailstringCustomer’s email. Left blank, it is filled from the payer identity Link returns (see Payer identity)
mockScenarioStripeLinkMockScenarioTesting only. StripeLinkMockScenario.Success or .Cancelled makes the server simulate the charge — the shopper still confirms through a real Link element. Import the enum from @payment-kit-js/vanilla/payment-methods/stripe-link; the bare string "success" does not type-check

Required here means required by the TypeScript type. The underlying API treats each customer_info field as individually optional, so a plain-JavaScript caller that omits a name gets a nameless customer rather than a validation error.

Error handling

Availability problems surface as a button that never appears, not as errors.

Treat onLinkReady as the availability signal: it fires true exactly once when Link is usable, and never fires at all when it is not — no Stripe processor, Link disabled, or Stripe.js missing. Render neither the express button nor the tile until onLinkReady fires.

Do not infer unavailability from onLinkAuthChange(false). It means “no authenticated Link session”, which is also the normal tile case, and on several initialization failure paths it never fires at all. Gating the tile on it alone renders a Link tile on checkouts where Link cannot be fulfilled.

ErrorSurfaces viaCause
Stripe Link cancelled by useronLinkResultShopper dismissed the Link dialog
Stripe Link is not available for this processoronLinkResultProcessor is not Stripe, or does not have stripe_link enabled. Silently swallowed during initialization — only reaches you on confirm
Processor not found: {id}onLinkResultThe processorId does not belong to the checkout’s account. Also swallowed during initialization
Processor {id} has no credentials configuredonLinkResultThe Stripe processor has no stored credentials
Stripe Link is not available: amount {n} is below the Stripe minimum of {m} for {CUR}.onLinkResultCheckout amount is under Stripe’s per-currency minimum. Checkout mode only — setup mode is exempt
payment_method_id is required to confirm Stripe Link checkoutonLinkResultConfirm posted without the Link pm_...
Stripe Link failed to create a payment methodonLinkResultStripe could not create the payment method and returned no message
Stripe Link failedonLinkResultAdapter reported a non-cancel failure with no message
Stripe Link payment failedonLinkResultFallback when the charge status is not success and no customer- or debug-facing message came back. Normally you receive the processor’s decline message instead
Failed to confirm Stripe Link payment / Request failed ({status})onLinkResultThe confirm request failed. In practice you receive the API’s detail message
Stripe Link not initialized — call setSubmitOptions firstonLinkResultLink state was cleared by teardownStripeLink() (or a PaymentKit-wide cleanup) before the shopper confirmed, or initStripeLink was never called. The setSubmitOptions in this message is stale — the method is initStripeLink
Stripe Elements not readyonLinkResultInternal guard — confirm fired without a live Stripe client. Should not occur in normal use
Express element not set upmountLinkButton return valuemountLinkButton called before initStripeLink resolved
Mount target not found: {selector}mountLinkButton return valueThe selector passed to mountLinkButton matches no element
Stripe Link is confirmed via its mounted express button, not paymentKit.submit(). Use initStripeLink() + mountLinkButton() and observe onLinkResult().submit onErrorpaymentKit.submit() called with paymentMethod: 'stripe_link'