External 3DS with the Juspay 3DS Server
Use this flow when the Juspay 3DS Server authenticates the cardholder and a separate payment processor authorizes the payment. Your backend creates and confirms the payment. Your browser then performs device fingerprinting, starts authentication, presents any challenge, and retrieves the final payment status.
This page owns the complete browser walkthrough for the Juspay 3DS Server. It covers both browser models:
Embedded iframe: the challenge stays on your checkout page. You listen for
postMessageevents and poll a lightweight status endpoint to determine the authentication status.Full-page redirect: the browser navigates to the ACS and returns to your
return_url. This is simpler to implement, but it does not provide an embedded experience.
The two models share the same setup, payment, fingerprinting, and authentication calls. They differ only in the target used for the final forms and how your application receives completion.
For the provider-neutral explanation, see Standalone 3D Secure. Mobile 3DS is outside this browser walkthrough.
How the integration works
A payment is created and confirmed on Hyperswitch. When external 3DS is requested, Hyperswitch uses Juspay ThreeDs server as authentication connector and returns a three_ds_invoke next action. Your frontend then completes device fingerprinting, calls the authentication endpoint, handles either a challenge or frictionless outcome, and retrieves the payment status.
Create and confirm a payment in Hyperswitch.
Receive
three_ds_invoke.Run device fingerprinting when required.
Call the 3DS authentication endpoint.
Handle challenge or frictionless authentication.
Finalize authorization and retrieve the payment status.
Before you start
Keep a secret API key on your backend and a publishable key in the browser. Never expose the secret API key in browser code.
Complete the four setup steps below in order. They enable the feature, add the 3DS metadata to your payment connector, register the Juspay 3DS Server, and point the Business Profile at it.
Step 1: Enable External 3DS for your account
Contact Hyperswitch Support to enable External 3DS. It can be enabled at either level:
Organization ID: External 3DS is enabled for the organization, and every Merchant ID under it is enabled by default.
Merchant ID: External 3DS is enabled only for the specified Merchant ID.
Step 2: Add External 3DS metadata to the payment connector
The Merchant Connector Account for the payment processor needs additional metadata for the External 3DS flow. If the connector already exists, update it rather than creating a second one.
The example below updates a Nuvei connector.
These fields are not exposed in the dashboard connector form. Set them through the connector update API as shown.
Step 3: Register the Juspay 3DS Server
Add the Juspay 3DS Server as an authentication connector. This is what lets Hyperswitch use it for External 3DS.
auth_type is NoKey: the connector configuration declares no credential fields for the Juspay 3DS Server, so the activation call carries metadata only. See the Juspay 3DS Server connector page.
Step 4: Enable 3DS authentication on the Business Profile
Point the profile at the Juspay 3DS Server. authentication_connector_details carries the ordered authentication connector list and the 3DS requestor URL.
The profile example sends merchant_country_code 004, reproduced from the source guide, while the connector examples in steps 2 and 3 send 840. The value is validated as a numeric ISO 3166 country code. Use your own merchant's code in all three places rather than copying either example value.
1. Create and confirm the payment
Set request_external_three_ds_authentication to true, use authentication_type: "three_ds", and provide a return_url.
Send browser_info. It is reused later for the authentication step in step 4, which does not accept browser data of its own. The v1 schema allows browser_info to be omitted, and the authentication transformer then supplies an empty browser object, so send the real browser values for a browser transaction rather than relying on that fallback.
A payment that needs this flow returns next_action.type as three_ds_invoke.
2. Read three_ds_invoke
The response shape is a tagged next_action object. poll_config.delay_in_secs and poll_config.frequency are integers here.
three_ds_method_details.three_ds_method_data_submission
Whether device fingerprinting in step 3 is required.
three_ds_method_url, three_ds_method_data, three_ds_method_key
Inputs for the fingerprinting POST. For the Juspay 3DS Server, three_ds_method_key is always threeDSMethodData.
three_ds_method_details.consume_post_message_for_three_ds_method_completion
For the Juspay 3DS Server this is always false.
three_ds_authentication_url
Endpoint for the authentication call in step 4.
three_ds_authorize_url
Endpoint to post the challenge or non-challenge result to in step 5.
poll_config
Poll id, delay and maximum attempts for the lightweight status poll. Used only in the embedded iframe model.
message_version, directory_server_id, card_network, three_ds_connector
Context returned for the selected 3DS path.
3. Perform device fingerprinting
Check three_ds_method_data_submission before creating the form.
If it is
false, skip the form and sendthreeds_method_comp_ind: "U"in step 4.If it is
true, submit the method data in a hidden iframe. Send"Y"when your method completion handling succeeds. Send"N"when it fails.
threeds_method_comp_ind has exactly three wire values:
Y
The 3DS method completed.
N
The 3DS method did not complete successfully.
U
The 3DS method URL was unavailable or no submission was requested.
The hidden iframe is required in both browser models. Fingerprinting must run invisibly in the background without navigating your page away.
Detecting completion. Watch for the hidden iframe navigating away from about:blank. Cross-origin access to the iframe throws a SecurityError, which you can treat as completion. Apply a timeout of about 15 seconds as a fallback, and send "N" if it expires.
4. Start 3DS authentication
Call POST /payments/{payment_id}/3ds/authentication with the publishable key. When the request does not use an SDK Authorization header, client_secret is required. Browser requests use device_channel: "BRW".
Do not send browser_info to this endpoint. Its request object has four fields: client_secret, sdk_information, device_channel, and threeds_method_comp_ind. The browser information comes from the payment created in step 1.
The response carries trans_status and, when needed, ACS challenge fields.
trans_status has eight wire values. The list below contains all eight values declared by the API enum.
Y
Authentication or account verification succeeded.
N
Authentication failed or the transaction was denied.
U
Authentication could not be performed.
A
An authentication attempt was recorded, but the cardholder was not verified.
R
The issuer rejected authentication and requests that authorization not be attempted.
C
A challenge is required.
D
Decoupled authentication is pending.
I
Informational response only.
Only C uses the ACS challenge form in this browser walkthrough. Do not treat every non-C value as success. Continue to the authorize URL so Hyperswitch can finalize the payment, then use the final payment status as the result.
5. Submit the challenge or non-challenge result
The payload is the same for both integration models. Only the form target changes.
Challenge response when trans_status is C
Submit challenge_request to acs_url. Name the field with challenge_request_key.
Embedded iframe
Full-page redirect
The ACS renders the challenge page, for example an OTP prompt. On completion it posts the result to three_ds_authorize_url, which returns the script that finalizes the flow.
Non-challenge response
Submit an empty form to three_ds_authorize_url.
Embedded iframe
This reuses the challenge iframe. Declare it here if your flow skipped the challenge path, or the form opens in a new window.
Full-page redirect
6. Complete the selected browser model
When the challenge or non-challenge result reaches three_ds_authorize_url, Hyperswitch responds with an HTML page containing a small script. That script detects at runtime whether it is running inside an iframe or in the top-level window, and behaves accordingly. This is why the same authorize URL serves both models. Use the section below that matches how you submitted the form in step 5.
Embedded iframe
The authorize response runs inside the iframe. It posts one of two message shapes to the parent window:
poll_statusmeans authorization is still being finalized.openurl_if_requiredcarries the return URL when no polling instruction is needed.
In poll_status, frequency and delay_in_secs are strings. This differs from the integer values in next_action.three_ds_data.poll_config.
Register the listener before submitting the challenge or authorize form. Validate event.origin against the origin of three_ds_authorize_url before using the message.
GET /poll/status/{poll_id} returns pending, completed, or not_found. It accepts the publishable key.
Full-page redirect
The authorize response runs in the top-level window and redirects to return_url, appending payment_id, status, payment_intent_client_secret, amount, and manual_retry_allowed as query parameters. No postMessage is sent and /poll/status is not used in this model.
The redirect is immediate. The status in the return URL is the status at the moment of redirect and may not be terminal yet, because the return URL does not wait for authorization to finalize. Confirm the final status yourself on your return page.
Ensure return_url is set on the confirm request in step 1. In this model it is load-bearing: the backend redirects the top-level window to it.
Read payment_id and payment_intent_client_secret from the return URL, then retrieve the payment, retrying until the status is terminal.
Retrieve the final payment
Both models finish by retrieving the payment with force_sync=true. The retrieve endpoint accepts the publishable key when the payment client secret is supplied.
Use the returned payment status to decide what your application shows next.
In the full-page redirect model, retry the retrieve until the status is terminal:
Sandbox testing notes
Test OTP: on the challenge page, choose any of the answers offered in the multiple-choice prompt.
Test card:
2221008123677736. This card is specific to Nuvei as the authorizing processor.
Last updated
Was this helpful?

