For the complete documentation index, see llms.txt. This page is also available as Markdown.

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.

  1. The payment itself

  2. The payment methods available for it, including the customer's saved cards

  3. 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

Client integration
Server integration

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.

Value
What comes back

server

The payment, plus payment_method_list and session_tokens

client, or absent

The payment response, unchanged

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.

Once your account allows the header, the sections come back only with merchant API key authentication. The update call also accepts a publishable key with a client secret, and a caller authenticated that way gets the ordinary response even if it sends server. An unrecognised value reads as client, which means a typo is rejected outright on a server account, and returns the ordinary response on a client_and_server one.

When create-side support ships, the header will apply to an unconfirmed intent: a create-and-confirm request, meaning one that sends "confirm": true, will return the ordinary response, because a payment that has already been confirmed has no use for a payment-method list or wallet session tokens. Today the create call returns the ordinary response either way, as the warning above describes.

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.

Omit customer_id from the request and Hyperswitch generates one for you. Reuse the same customer_id across visits so saved cards follow the customer.

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.

For the full SDK walkthrough, including appearance customization and error handling, see Vault SDK Integration.

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.

Last updated

Was this helpful?