Server to Server Payments
Drive checkout from your own backend and fetch the payment, its payment methods and the wallet session tokens in a single call
In a server-to-server integration your backend orchestrates checkout. It holds the merchant API key, calls Hyperswitch, and hands the result to whatever renders the payment screen: your own UI, a mobile app, or a partner surface.
The difficulty is that a checkout screen needs three things before it can render anything.
The payment itself
The payment methods available for it, including the customer's saved cards
The wallet session tokens
Fetched separately, that is three sequential round trips, with the client secret threaded through two of them. Sending X-Integration-Type: server collapses them into one.
How it compares
The SDK runs in the browser or app and fetches what it needs
Your backend fetches everything and renders checkout itself
Three calls to gather what checkout needs
One call to gather what checkout needs
Publishable key and client secret reach the client
Merchant API key stays on your server
Header is client, or absent
Header is server
Reach for a client integration when the Hyperswitch SDK drives checkout, because it already fetches what it needs. Reach for a server integration when your backend is in charge and you would rather gather everything in one call than three.
The header
X-Integration-Type is accepted on both the create and the update call.
server
The payment, plus payment_method_list and session_tokens
client, or absent
The payment response, unchanged
The create call does not return the sections yet. Both calls validate the header today, but only the update call builds payment_method_list and session_tokens. Create-side support is juspay/hyperswitch#14172, still open at the time of writing.
Until it ships, create the payment, then make one update call with the header and take both sections from that response. Step 3 is that call, and it works on any intent field, so you can send the same amount you created with. This page will lose the extra call when #14172 lands.
Your merchant account has to allow it
Every create and update call checks the header against your account's configured integration type, and rejects a mismatch with a 422 before anything else happens. The default is client, so a new account rejects server until it is reconfigured.
Your integration type
server
client, or absent
client (the default)
Rejected
Accepted
server
Accepted
Rejected
client_and_server
Accepted
Accepted
The rejection reads:
Ask your Hyperswitch contact to set the account to server or client_and_server before you start.
Step 1: Create the customer
Create the customer first so the payment can be attached to it. This is what lets the payment-method list come back with the customer's saved cards, and what lets a card tokenized during this checkout be reused later.
Keep the customer_id. If you already have one, skip this step and reuse it.
Step 2: Create the payment
Create the intent against that customer, without confirming it, with the header set.
The response is the payment you already know, with two sections added once #14172 ships. Until then this call returns the ordinary payment response, and Step 3 returns the sections shown here. Keep the payment_id, and keep session_tokens.vault_details for Step 4.
payment_method_list is the object List payment methods for a payment returns, including intent_data, which repeats the intent fields the checkout screen needs so you do not have to thread them through yourself. session_tokens is the same object Create session tokens returns. Anything already parsing those responses works unchanged.
A returning customer's saved cards arrive in customer_payment_methods, each with a payment_token. If the customer picks one of those, skip Step 4 and confirm with that token. Check requires_cvv first: when it is true, collect the CVV and send it alongside the token, as the saved card example shows.
Step 3: Update the payment
Send the same header on an update and both sections come back, built against the intent's current values. Use this response for the rest of the flow: its session_tokens and payment_method_list supersede anything from Step 2.
Run this step when a field on the intent changes, for example the basket total or the currency. Until #14172 ships, run it even when nothing changed, because it is the call that returns the sections; send the values you already created with.
Step 4: Collect the card with the Payment Methods SDK
Card details never touch your server. The sdk_authorization inside vault_details is the vault session the Payment Methods SDK needs, so you do not have to call /payment-method-sessions separately — the call you just made already handed it to you. Take it from the most recent response: Step 3's if you ran it, otherwise Step 2's.
vault_details is optional, and session_tokens can arrive carrying an error instead of the tokens, as When a section cannot be built describes. Read session_tokens.vault_details.vault_data.sdk_authorization defensively, and when it is not there, fall back to creating the session yourself with /payment-method-sessions before mounting the widget.
Pass it to your frontend, add a placeholder for the widget, and mount it.
When the customer submits, call confirmTokenization(). The card is tokenized inside the Hyperswitch-hosted iframe and response.id comes back as the payment_method_id for the vaulted card.
YOUR_PUBLISHABLE_KEY comes from the dashboard under Developers, and YOUR_PROFILE_ID is the profile_id on the Step 2 response. Never send your API key to the browser.
Step 5: Confirm
Confirm from your server with the merchant API key and the identifier your frontend sent back from Step 4. confirmTokenization() returns it as response.id, the payment_method_id for the card it just vaulted, and the confirm call's payment_token accepts that identifier, the same way the Token Led Payment flow uses it.
Confirming a saved card
A returning customer who picked a saved card never goes through Step 4. Confirm with that card's payment_token from customer_payment_methods instead.
When the card came back with "requires_cvv": true, the CVV has to travel with the confirm call, in payment_method_data.card_token. Collect it through the SDK's card element rather than a form of your own, so the raw value is tokenized in the Hyperswitch iframe and your server forwards a token; card_token also accepts card_cvc_token for that. Handling the raw CVV on your server puts that request in PCI scope.
Cards that come back with "requires_cvv": false confirm with the token alone.
The amounts here are the ones the payment was created with. If Step 3 changed the amount, the confirmed amount is the updated one.
A status of succeeded means the payment is done. If the card needs 3DS, status comes back as requires_customer_action with a next_action telling you where to send the customer.
When a section cannot be built
The payment write has already committed by the time these sections are built. A section that fails therefore reports its own error inline rather than failing the whole request, so a committed state change is never hidden from you by a problem in a read that came after it.
Check each section for an error key before using it. The payment is unaffected, so you can proceed with the section that arrived and either retry the other or fall back to its standalone endpoint.
Adding the header never changes the payment itself. The same request sent with client, or with no header at all, does exactly the same thing to the payment; only the response shape differs. An existing integration that has never heard of this header keeps working on a client or client_and_server account. Switching an account to server is the breaking move: that account then rejects calls without the header, so migrate your callers before asking for the switch, or use client_and_server while you do.
Related
Last updated
Was this helpful?

