SDK Payment flows
Payments flow
There are multiple stages in a Payment flow depending on the payment methods that are involved. Considering an one-time payment method where there was no redirection involved, the following stages form the Payment flow:
a) Creating a Payment: When your customer wants to checkout, create a payment by hitting the payments/create endpoint. Fetch and store the payment_id and client_secret
b) Loading the SDK: After your customer checks out, load the Hyperswitch SDK by initiating it with the client_secret and publishable_key
c) SDK being rendered: After you initiate the SDK, the SDK makes several API calls involving the /sessions and /payment_methods endpoints to load relevant payment methods and any saved cards associated with the customer
d) Customer enters the payment method data: After the SDK is fully rendered, your customer would choose a payment method and enter the relevant information and click pay
e) Confirming the payment: After the customer clicks pay, the SDK calls the payments/confirm endpoint with the customer's payment method details and post response, it displays the payment status

Here's a more detailed version of the payment flow:
How does Payment flow vary across Payment methods?
Customer action required before payments/ confirm
Within Hyperswitch SDK
Non 3DS Cards
Bank Debits like ACH Debit, BACS Debit, SEPA Debit
Customer action required before payments/ confirm
3rd party Redirect/SDK
Wallets like Apple Pay, Google pay, Paypal, AliPay
BNPL like Klarna, Afterpay, Affirm
Customer action required after payments/ confirm
3rd party Redirect
3DS cards
Bank Redirects like iDeal, Giropay, eps
Bank Transfers like ACH Transfer, SEPA Transfer, BACS Transfer, Multibanco
Crypto wallets like Cryptopay
Functionalities provided by Hyperswitch
Which SDK surface to reach for
The stages above describe the full checkout sheet. Four other surfaces exist, and picking the wrong one costs a rewrite. Each row is the SDK source of truth, not a spec claim.
The whole payment sheet, Hyperswitch renders it
Payment element
elements.create("payment") on Web. On React Native, PaymentElement embeds the sheet in your screen, while paymentSession.presentPaymentSheet(...) presents it as a drop-in
One payment method, your own layout around it
Single element
elements.create("card") for a whole card form, or cardNumber, cardExpiry and cardCvc mounted separately when you lay the fields out yourself
A wallet button on its own, no sheet
Wallet element
One name per call, for example elements.create("googlePay"). The others are applePay, payPal, samsungPay, paze and expressCheckout
Saved methods as data, you draw the UI
Headless SDK
hyper.initPaymentSession(...) on Web; on React Native Hyperswitch.init(...) first, then initPaymentSession(...) on what it returns. A saved card with requires_cvv still needs the SDK's CVC element
Collect and vault a card with no payment attached
Payment method session
hyper.initPaymentMethodSession(...)
elements.create accepts 14 names in all. The other three are paymentMethodCollect, klarna and paymentMethodsManagement. Anything else logs an unknown-key warning and creates nothing.
Take React Native to production
The quickstart gets a sheet on screen. Four things separate that from a production app.
Install the peer dependencies yourself. @juspay-tech/react-native-hyperswitch declares 6 peer dependencies and bundles none of them: @sentry/react-native, react, react-native, react-native-inappbrowser-reborn, react-native-svg, react-native-webview. Install all six. Missing ones surface early rather than at runtime: react and react-native fail at module resolution, and the Android sources import react-native-inappbrowser-reborn classes directly, so leaving it out fails the native build.
Add the companion packages you need. Apple Pay and Google Pay are already in the base package, which exports GooglePayButton, ApplePayButton and the support checks. Eight more packages ship alongside it. Install only the ones you use, because each one adds native weight to your build.
@juspay-tech/react-native-hyperswitch-click-to-pay
Click to Pay
@juspay-tech/react-native-hyperswitch-samsung-pay
Samsung Pay
@juspay-tech/react-native-hyperswitch-paypal
PayPal
@juspay-tech/react-native-hyperswitch-netcetera-3ds
Netcetera 3DS authentication
@juspay-tech/react-native-hyperswitch-trident-3ds
Trident 3DS authentication
@juspay-tech/react-native-hyperswitch-scancard
Card scanning
@juspay-tech/react-native-hyperswitch-vault
Hyperswitch's own card form
@juspay-tech/react-native-hyperswitch-payment-methods
Card collection widgets for an external vault, covering VGS, Skyflow, Basis Theory and Evervault behind one interface
Choose from your authentication setup as well as your payment methods. The 3DS packages are easy to miss, because nothing about a card payment says out loud that it needs one.
Set the environment explicitly. Hyperswitch.init takes environment, one of PROD, SANDBOX or INTEG. It defaults to PROD. A sandbox build that forgets the field points at production.
Create the session on your server. initPaymentSession takes sdkAuthorization, not a client secret. Your backend creates the payment and returns that string. The secret API key never reaches the app.
Step-by-step integration, Expo and customization are in the React Native guide.
Collect a CVC for a saved card on the Web SDK
Use a payment method session, not the payment sheet. hyper.initPaymentMethodSession({ sdkAuthorization }) returns a session that exposes createCardForm, and the form you build decides what happens on tokenize().
One thing to get right first: the sdkAuthorization here is not the one a payment hands you. The two look alike, so they are easy to mix up. The SDK decodes the string and looks inside it for a payment_method_session_id, and a payment's authorization carries a payment_id instead. Pass the wrong one and the session id comes back empty, so the card form never initializes. Create a payment method session for the customer on your backend first, with POST /v2/payment-method-sessions, and hand the SDK an authorization for that session.
Mount
cardCvcon its own andtokenize()runs the update flow against the saved card, which is the CVC-refresh case.Mount
cardNumberandtokenize()runs the save flow, vaulting a new card.Mount nothing, or
cardExpiryalone, andtokenize()returnsincomplete_field_set.
So the CVC-only form is not a mode you switch on. It is what you get by mounting exactly one field. The session also exposes update, on, deinit and fields, and the form adds create, on, tokenize, deinit, update and fields.
Mount a wallet button on its own
Both platforms let you put Apple Pay or Google Pay on a page that has no payment sheet.
On Web, create the element by name: googlePay, applePay, payPal, samsungPay or paze, or expressCheckout for the row of every eligible wallet. The button confirms the payment through the SDK.
On React Native, render GooglePayButton or ApplePayButton. isGooglePaySupported, isApplePaySupported, isWalletSupported and isPlatformPaySupported answer whether the device can present the wallet at all, which is worth checking first because a button for a wallet the device cannot present is a dead button. Whether this payment is eligible is a separate answer, and it comes from the wallet session.
To drive the sheet from your own button, render inside <HyperElements> and call load() from useWalletSession(). The hook throws if you use it outside that provider.
The hook hands you { walletSession, isGooglePayEligible, isApplePayEligible, loading, load }. The two eligibility flags are the per-payment answer: whether this payment can use that wallet. The launch methods are not on the hook. They live on the handle that load() resolves to, which is also kept in walletSession afterwards, and that handle is what carries launchWallet, launchGooglePay and launchApplePay.
Check the handle before you use it. load() never throws; when anything goes wrong it resolves to null instead. A session that cannot bootstrap within 15 seconds comes back that way, and so does calling load() before the payment session is ready. You get the same null either way, with nothing to tell the two apart, so log around the call if you need to know which happened.
Get the hosted payment link out of the create response
Send payment_link: true on POST /payments and the response carries a payment_link object. The hosted URL is payment_link.link. The object also carries secure_link, the URL for the secure variant, and payment_link_id.
secure_link is always in the response: it carries the secure URL when the profile is configured for secure links, and is null when it is not. Send payment_link_config on the same request to theme the page. Configuration, theming and custom domains are in Payment Links.
What the Headless SDK exposes
The Headless SDK gives you a customer's saved payment methods as data and leaves the UI to you. One piece stays Hyperswitch's: a saved card that comes back with requires_cvv is confirmed by passing the id of a mounted CardCVCElement, so the CVC is collected in the SDK's element rather than an input of your own. Everything around it is yours to draw.
On Web, hyper.initPaymentSession({ clientSecret }) returns a session with two members, getCustomerSavedPaymentMethods and updateIntent. Await getCustomerSavedPaymentMethods() and the object it resolves to is the one that carries both the saved method data and the confirm functions, confirmWithCustomerDefaultPaymentMethod and confirmWithLastUsedPaymentMethod. Call them on that object, not on the session.
On React Native, Hyperswitch.init(...) then initPaymentSession({ sdkAuthorization }) returns a session exposing presentPaymentSheet, getCustomerSavedPaymentMethods, getWalletSession and updateIntent. Await getCustomerSavedPaymentMethods() for the saved-methods session, which carries three getters, getCustomerLastUsedPaymentMethodData, getCustomerDefaultSavedPaymentMethodData and getCustomerSavedPaymentMethodData, and the confirm functions, confirmWithCustomerLastUsedPaymentMethod and confirmWithCustomerDefaultPaymentMethod. The declared type marks the default-method one optional, so guard it before calling rather than assuming it is there. Each getter resolves to a payment method or null, and its card field is itself nullable, so do not assume a card came back.
It only covers already-saved methods. A first-time card still needs a card form, which means an element or the payment sheet.
Per-platform Headless guides: Web, Android, iOS, React Native and Flutter.
Questions this page does not answer
Where the PCI SAQ-A boundary sits when you use the hosted SDK. Hyperswitch has not published a statement of that boundary, so this page does not state one. Security and Compliance covers how PCI DSS assessment works, SAQ against ROC, and when a QSA is required. Your assessment level is a QSA question.
Driving checkout entirely from your backend. That is Server to Server Payments, where one call returns the payment together with its payment-method list and the wallet session tokens. That page says which call does it today.
Refunding a payment made through the SDK. The SDK plays no part; see Refunds.
What are PaymentIntent and PaymentAttempt objects and how do they work in Hyperswitch?
Hyperswitch uses the PaymentIntent object to track the status of a payment initiated by you. Since, Hyperswitch enables retrying a single payment multiple times across different processors until a successful transaction, we track each of these payment attempts through separate PaymentAttempt objects.
While PaymentIntent and PaymentAttempt have their own state machines, the various states in PaymentAttempt are also constrained by their respective mapping to the PaymentIntent statuses.
PaymentIntent state machine:
The following is an abridged version of the PaymentIntent state machine flow that covers majority of the above payment use-cases.
PaymentAttempt state machine:
The following is an abridged version of the PaymentAttempt state machine flow that covers majority of the above payment use-cases.
Last updated
Was this helpful?








