3D Secure Integration

Present 3D Secure challenges at checkout with the Preczn browser SDK, so card payments that need cardholder authentication complete instead of declining.

3D Secure (3DS) lets a card issuer ask the cardholder to verify a payment before it is approved, for example with a one-time code or a banking app prompt. Without 3DS, a card that requires this verification is declined. With 3DS, Preczn returns a challenge. Your page presents it in a Preczn-hosted lightbox, and the payment then completes.

Transactions remain server-to-server: your server creates the transaction with your private API key, exactly as it does today. The browser presents the challenge and never creates a transaction.

ℹ️ Supported connections

3D Secure challenges are currently supported on Stripe and Stripe Connect connections. Card payments only: Apple Pay and Google Pay payments are processed without a challenge.

How It Works


Eligibility

A challenge can only happen when payment is a single-use token created by the Preczn browser SDK (PaymentElements or PaymentFields).

A challenge needs the cardholder at a live checkout, on a page that can show it. A single-use token minted by the browser SDK moments ago is how Preczn knows that is true. The following are processed as none, whatever requestThreeDSecure value you send:

  • Multi-use tokens (card-on-file, recurring). The cardholder is usually not present to complete a challenge.
  • Raw card data sent from your server. It was not captured at a live checkout by the Preczn SDK.

These requests are not rejected. They run as they do today, so a card that requires a challenge declines. To make a raw-card integration eligible, tokenize with the Preczn browser SDK.

Integration Sequence

  1. Your page tokenizes the card with the Preczn browser SDK and sends the single-use token to your server.
  2. Your server calls POST /v1/transactions with the token and requestThreeDSecure.
  3. If the issuer requires a challenge, the transaction is returned with authorization.status P (pending) and an authorization.threeDSecureChallenge object with status CHALLENGE_REQUIRED.
  4. Your server passes the transaction id and authorization.threeDSecureChallenge to your page, in whatever response your checkout endpoint already returns.
  5. Your page calls Preczn.ThreeDSecure.handleChallenge(id, threeDSecureChallenge.challengeKey, callback). The SDK opens a lightbox where the cardholder completes the issuer's challenge.
  6. The SDK closes the lightbox and invokes your callback with the outcome.
  7. Your server confirms the final result with GET /v1/transactions/:id.
sequenceDiagram
    autonumber
    actor Cardholder
    participant Page as Your Page
    participant SDK as Preczn SDK
    participant Server as Your Server
    participant Preczn as Preczn API
    participant Issuer as Card Issuer

    Cardholder->>Page: Enters card details
    Page->>SDK: Tokenize card
    SDK-->>Page: Single-use token (tkn_...)
    Page->>Server: Submit checkout with token
    Server->>Preczn: POST /v1/transactions<br/>requestThreeDSecure: "automatic"
    Preczn-->>Server: authorization.status "P"<br/>threeDSecureChallenge: CHALLENGE_REQUIRED
    Server-->>Page: Transaction id + threeDSecureChallenge
    Page->>SDK: handleChallenge(id, challengeKey, callback)
    SDK->>Cardholder: Opens challenge lightbox
    Cardholder->>Issuer: Completes challenge
    SDK-->>Page: callback({ transactionId, status, threeDSecureStatus })
    Page->>Server: Confirm order
    Server->>Preczn: GET /v1/transactions/:id
    Preczn-->>Server: Final transaction (approved or declined)

If the issuer does not require a challenge, step 3 returns a final status (A or D) directly, and steps 4 to 6 are skipped.

API Contract


Request: requestThreeDSecure

Add the optional requestThreeDSecure field to a sale or auth request on POST /v1/transactions. It is a string, never an object. Any other value, including an object or an empty string, returns a 400.

ValueBehavior
(omitted)Same as none. This is the default.
noneNo 3D Secure. A card that requires a challenge declines.
automaticYour page can present a challenge. The processor decides whether one is needed.
challengeSame as automatic, and additionally asks the issuer to authenticate the cardholder. The issuer still decides whether the cardholder sees a challenge.
{
  "merchantId": "mrc_test_5kpqt630zh9kdrev2kxmdr66tr",
  "type": "sale",
  "amount": 1234,
  "currency": "USD",
  "payment": { "token": "tkn_test_8rd2m4ka7n1xq0zj5c3vbe9hw6" },
  "requestThreeDSecure": "automatic"
}

ℹ️ For most integrations, use automatic. Use challenge only when you have a reason to request authentication on every eligible payment.

Response: authorization.threeDSecureChallenge

authorization.threeDSecureChallenge appears only when 3D Secure was requested and the processor returned a challenge or an authentication result.

When a challenge is required, the POST /v1/transactions response looks like this:

{
  "id": "txn_test_5kpqt630zh9kdrev2kxmdr66tr",
  "authorization": {
    "status": "P",
    "threeDSecureChallenge": {
      "status": "CHALLENGE_REQUIRED",
      "challengeKey": "9f2c4e6a8b0d1f3e5a7c9b1d3f5e7a9c2b4d6f8e0a1c3e5f7b9d1a3c5e7f9b2d",
      "expiresAt": "2026-10-19T14:30:00.000Z"
    }
  }
}

After the challenge resolves, GET /v1/transactions/:id returns the authentication result:

{
  "id": "txn_test_5kpqt630zh9kdrev2kxmdr66tr",
  "authorization": {
    "status": "A",
    "threeDSecureChallenge": {
      "status": "AUTHENTICATED",
      "result": "authenticated",
      "authenticationFlow": "challenge",
      "version": "2.2.0"
    }
  }
}
FieldTypeDescription
statusstringThe 3D Secure status. See 3D Secure Statuses.
challengeKeystringThe key you pass, with the transaction id, to Preczn.ThreeDSecure.handleChallenge. Returned only on the POST /v1/transactions response while status is CHALLENGE_REQUIRED. It is not returned by GET /v1/transactions/:id.
expiresAtstringISO-8601 time after which the challenge can no longer be completed (30 minutes after the transaction is created). Returned with CHALLENGE_REQUIRED.
resultstringThe raw 3D Secure result from the processor (for Stripe: authenticated, attempt_acknowledged, exempted, failed, not_supported, processing_error).
resultReasonstringThe processor's reason for the result, when one is given (for example card_not_enrolled).
authenticationFlowstringchallenge if the cardholder completed a challenge, frictionless if the issuer authenticated them without one.
versionstringThe 3D Secure protocol version used (for example 2.2.0).

ℹ️ There is no challenge URL in the response. The SDK builds the challenge page address itself from the transaction id and challengeKey.

3D Secure Statuses

StatusMeaning
CHALLENGE_REQUIREDThe cardholder must complete a challenge before the transaction can finish. Present it with handleChallenge.
AUTHENTICATEDThe issuer verified the cardholder.
ATTEMPTEDAuthentication was attempted, but the issuer did not complete it. The payment can still proceed.
EXEMPTEDThe transaction qualified for an exemption and did not need authentication.
FAILEDThe cardholder failed authentication.
NOT_PERFORMEDAuthentication was not supported for this card or could not be performed.
UNKNOWNThe transaction resolved, but the processor returned no 3D Secure result.

Browser SDK


Load the SDK

Add the 3ds resource to the Preczn script you already load for tokenization, using your public API key:

<script src="https://api.preczn.com/v1/clients/preczn.min.js?resources=paymentelements,3ds&publicApiKey=pub_test_xxxxxxxxxxxxxxxx"></script>

The SDK exposes window.Preczn.ThreeDSecure. When it is ready, it sets Preczn.ThreeDSecure.ready to true and dispatches a Preczn.ThreeDSecure.ready event on document:

document.addEventListener('Preczn.ThreeDSecure.ready', function () {
  // handleChallenge is available
});

⚠️ HTTPS is required

handleChallenge runs only on pages served over HTTPS. Your page's Content Security Policy must allow the Preczn origin in script-src and frame-src (for example https://api.preczn.com), as for PaymentElements.

handleChallenge

Preczn.ThreeDSecure.handleChallenge(transactionId, challengeKey, callback, options);
ParameterTypeDescription
transactionIdstringThe transaction id from the POST /v1/transactions response.
challengeKeystringauthorization.threeDSecureChallenge.challengeKey from the same response.
callbackfunctionCalled once with (result, errors) when the challenge ends.
optionsobjectOptional. timeoutMs: how long to wait before closing the lightbox. Defaults to, and is capped at, 30 minutes.
// `checkout` is whatever your checkout endpoint returned:
// the transaction id and authorization.threeDSecureChallenge from Preczn.
Preczn.ThreeDSecure.handleChallenge(
  checkout.id,
  checkout.threeDSecureChallenge.challengeKey,
  function (result, errors) {
    if (errors) {
      // Cancelled, timed out, expired, or failed to open. See the table below.
      showError(errors[0]);
      return;
    }
    // A UX signal only: confirm on your server before fulfilling the order.
    confirmOrderOnServer(result.transactionId);
  }
);

The SDK opens a full-screen lightbox (a 600 × 400 challenge panel, full-screen on small screens) with a × button to cancel. Only one challenge can be open at a time.

Callback Result

On completion, result is:

FieldDescription
transactionIdThe transaction id.
statusThe transaction status: A (approved), D (declined), E (error) or P (still pending).
threeDSecureStatusThe 3D Secure status, for example AUTHENTICATED or FAILED.

After the cardholder finishes, the SDK waits up to about 20 seconds for the final result. If the processor has not confirmed it by then, status is P. The transaction is still resolving, and GET /v1/transactions/:id or the transaction webhook will give you the final status.

Errors, Cancellation, and Timeouts

If the challenge does not complete, result is null and errors is an array containing one message:

MessageWhen it happens
3D Secure challenge cancelledThe cardholder closed the lightbox (× button or Escape).
3D Secure challenge timed outoptions.timeoutMs (default 30 minutes) passed before the challenge finished.
This authentication request has expiredThe challenge was opened after expiresAt.
Challenge not foundThe transactionId and challengeKey do not match a challenge for your public API key.
A 3D Secure challenge is already in progresshandleChallenge was called while another challenge was open.
Invalid transactionId / Invalid challengeKeyAn argument is not in the expected format.
3D Secure can only run on a secure (HTTPS) connectionThe page is not served over HTTPS.
3D Secure challenge is not availableThe SDK could not determine the Preczn challenge origin.
Preczn 3DS unavailable: ...The SDK could not load, for example because of an invalid public API key.

A handleChallenge call with no callback function throws an error.

⚠️ Cancellation and timeout do not cancel the transaction

The transaction stays P. If the cardholder abandons the challenge and it is never completed, the transaction remains P after expiresAt; Preczn does not expire it automatically. Do not fulfill the order unless the transaction is approved.

Confirm the Outcome


The callback is a signal for your checkout UI, not proof of payment. Before you fulfill an order, confirm the result from your server with GET /v1/transactions/:id using your private API key. authorization.status gives the final result, and authorization.threeDSecureChallenge holds the authentication result.

We recommend also subscribing to the transaction.approved and transaction.declined webhooks. If the cardholder completes the challenge but closes the browser before your callback runs, the webhook still delivers the final status, so you can reconcile abandoned checkouts. Webhooks are not required for the normal flow.

Testing


Use these Preczn test cards with a test Merchant on a Stripe or Stripe Connect test connection. Preczn maps each one to the Stripe-published test card shown.

ScenarioPreczn test cardStripe test cardResult
3D Secure challenge43210000000000384000000000003220Returns CHALLENGE_REQUIRED. Complete the challenge in the lightbox to approve, or fail it to decline.
Generic decline43210000000000204000000000000002Declines.

⚠️ Tokenize test cards in the browser

Enter the test card through PaymentElements or PaymentFields so the transaction uses a single-use token, and send requestThreeDSecure as automatic or challenge. A test card sent as raw card data, or saved as a multi-use token, is processed as none and declines instead of showing a challenge.


Did this page help you?