Skip to content

3D Secure (3DS) Pass-Through with PAAY JS SDK

This guide covers how to collect 3D Secure authentication data using PAAY's standalone JavaScript SDK and pass it through to our /api/transaction endpoint. This approach is ideal when you are building your own payment form and want full control over the 3DS authentication flow, rather than using our Tokenizer.

When to Use This Approach

ApproachBest For
Tokenizer with built-in 3DSStandard integrations where you want the gateway to handle everything — card collection, tokenization, and 3DS — in a single iframe. See Tokenizer 3DS docs.
PAAY JS SDK + API pass-through (this guide)Custom payment forms where you need full control over the checkout experience, are already PCI-compliant (SAQ D), or need to integrate 3DS into an existing payment flow that calls the Gateway API directly.

Overview

The pass-through integration works in three stages:

  1. Authenticate — PAAY's JavaScript SDK authenticates the cardholder with the card issuer on your checkout page.
  2. Collect — After authentication, PAAY returns the 3DS values (CAVV, ECI, XID/dsTransId) to your frontend.
  3. Submit — Your server includes these 3DS values in the transaction request to our /api/transaction endpoint.
┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│  Your Checkout │──▶│  PAAY 3DS    │──▶│  Card Issuer │──▶│  3DS Result  │
│  Page          │   │  JS SDK      │   │  (ACS)       │   │  (CAVV/ECI)  │
└──────────────┘    └──────────────┘    └──────────────┘    └──────┬───────┘


                                                          ┌──────────────┐
                                                          │  Your Server │
                                                          │              │
                                                          │  POST to     │
                                                          │  Gateway     │
                                                          │  /api/       │
                                                          │  transaction │
                                                          └──────────────┘

Prerequisites

  1. PAAY account — You need an active account with PAAY (3DS Integrator). Contact your account representative or PAAY directly at info@paay.co to get set up.
  2. PAAY API key — Found in the PAAY Portal. This is separate from your Gateway API key.
  3. Gateway API key — Your secret API key (api_XXXX) for server-side transaction processing.
  4. PCI compliance — Since you're collecting card data in your own form (not via Tokenizer), your environment must meet the appropriate PCI compliance requirements (typically SAQ D).

Step 1: Set Up Your Payment Form

Your payment form must include card inputs tagged with data-threeds attributes so the PAAY SDK can read them. You also need hidden fields for the transaction ID and amount.

html
<form id="checkout-form" action="/api/charge" method="post">
  <!-- Cardholder Information -->
  <label>Card Number</label>
  <input type="text" name="cardNumber" maxlength="16" data-threeds="pan" />

  <label>Expiration Month</label>
  <select name="cardMonth" data-threeds="month">
    <option value="">--</option>
    <option value="01">01</option>
    <option value="02">02</option>
    <!-- ... -->
    <option value="12">12</option>
  </select>

  <label>Expiration Year</label>
  <select name="cardYear" data-threeds="year">
    <option value="">--</option>
    <option value="25">2025</option>
    <option value="26">2026</option>
    <!-- ... -->
    <option value="30">2030</option>
  </select>

  <label>CVV</label>
  <input type="text" name="cvv" maxlength="4" />

  <!-- Hidden 3DS fields -->
  <input type="hidden" name="x_transaction_id" value="" data-threeds="id" />
  <input type="hidden" name="paayAmount" value="25.00" data-threeds="amount" />

  <!-- 3DS results will be injected here by the SDK -->
  <!-- (cavv, eci, xid are auto-appended when appendForm is true) -->

  <button type="submit" id="pay-button">Pay $25.00</button>
</form>

Required data-threeds Attributes

AttributeInputDescription
data-threeds="pan"Card numberThe full credit card number.
data-threeds="month"Expiration month1 or 2 digit month (e.g., 01, 12).
data-threeds="year"Expiration year2 digit year (e.g., 25, 30).
data-threeds="id"Transaction IDA unique identifier for this 3DS authentication session. Auto-generated — see Step 2.
data-threeds="amount"Transaction amountThe transaction amount in decimal format (e.g., 25.00).

Step 2: Include the PAAY JS SDK

Add the PAAY JavaScript library and initialization script before the closing </body> tag:

html
<!-- PAAY 3DS JavaScript SDK -->
<script src="https://cdn.3dsintegrator.com/threeds.min.latest.js"></script>

<!-- Generate a unique transaction ID for this 3DS session -->
<script>
  var idInput = document.querySelector('[data-threeds=id]');
  idInput.value = 'id-' + Math.random().toString(36).substr(2, 16);
</script>

<!-- Initialize the PAAY ThreeDS SDK -->
<script>
  var tds = new ThreeDS(
    'checkout-form',                    // Your form's ID attribute
    'YOUR_PAAY_API_KEY',                // Your PAAY API key from the PAAY Portal
    null,                               // Pass null to let the SDK generate the JWT
    {
      autoSubmit: false,                // We'll trigger authentication manually
      endpoint: 'https://api-sandbox.3dsintegrator.com/v2.2',  // Sandbox endpoint
      verbose: true                     // Enable console logging for debugging
    }
  );
</script>

SDK Constructor Parameters

ParameterDescription
formIdThe id attribute of the <form> element containing your card inputs.
apiKeyYour PAAY API key from the PAAY Portal.
jwtPass null to let the SDK generate a JWT automatically. Or provide your own server-generated JWT.
optionsConfiguration object — see below.

SDK Options

OptionDefaultDescription
autoSubmittrueWhen true, authentication triggers automatically as the customer fills in card fields. Set to false for manual control via tds.verify().
endpoint(production)The PAAY API endpoint. Use https://api-sandbox.3dsintegrator.com/v2.2 for testing, and the production endpoint for live transactions.
verbosefalseEnable detailed console logging for debugging.
appendFormtrueAutomatically inject cavv, eci, and xid as hidden inputs in your form after authentication.
eciInputIdeciThe name attribute for the auto-appended ECI hidden input.
cavvInputIdcavvThe name attribute for the auto-appended CAVV hidden input.
xidInputIdxidThe name attribute for the auto-appended XID hidden input.
rebillfalseSet to a decimal amount (e.g., 89.95) to also authenticate a rebill amount. Returns rebill_cavv, rebill_eci, rebill_xid.

Step 3: Trigger Authentication

With autoSubmit: true (the default), the PAAY SDK automatically begins authentication as soon as the customer finishes entering their card number, expiration month, and expiration year. The 3DS values are injected into your form as hidden inputs.

javascript
var tds = new ThreeDS(
  'checkout-form',
  'YOUR_PAAY_API_KEY',
  null,
  {
    autoSubmit: true,
    endpoint: 'https://api-sandbox.3dsintegrator.com/v2.2',
    verbose: true
  }
);

When the customer clicks your submit button, the cavv, eci, and xid values will already be present as hidden fields in the form (assuming authentication completed).

With autoSubmit: false, you call tds.verify() yourself — typically when the customer clicks the pay button. This gives you control over when authentication happens and lets you handle the result before submitting.

javascript
var tds = new ThreeDS(
  'checkout-form',
  'YOUR_PAAY_API_KEY',
  null,
  {
    autoSubmit: false,
    endpoint: 'https://api-sandbox.3dsintegrator.com/v2.2',
    verbose: true
  }
);

document.getElementById('pay-button').addEventListener('click', function(e) {
  e.preventDefault();

  // Optional: Only authenticate Visa and Mastercard
  var cardNumber = document.querySelector('[data-threeds=pan]').value;
  var firstDigit = cardNumber.charAt(0);

  if (firstDigit === '4' || firstDigit === '5' || firstDigit === '2') {
    // Visa (4) or Mastercard (5, 2) — run 3DS authentication
    tds.verify({
      resolve: function(data) {
        console.log('3DS Authentication Result:', data);
        console.log('Status:', data.status);        // Y, A, N, U, R
        console.log('ECI:', data.eci);               // e.g., "05"
        console.log('CAVV:', data.authenticationValue);

        if (data.status === 'Y' || data.status === 'A') {
          // Authentication successful — submit payment
          submitPayment(data);
        } else {
          // Authentication failed — decide whether to proceed
          alert('3DS authentication was not successful. Status: ' + data.status);
        }
      },
      reject: function(error) {
        console.error('3DS Error:', error);
        // Decide whether to proceed without 3DS or show error
        alert('3DS authentication could not be completed.');
      }
    });
  } else {
    // Amex (3), Discover (6), or other — submit without 3DS
    submitPayment(null);
  }
});

Step 4: Handle the 3DS Response

After a successful authentication, the PAAY SDK returns a response object. The key fields you need for the gateway API are:

3DS Response Fields

FieldDescriptionUse in API
authenticationValueThe CAVV (Cardholder Authentication Verification Value). A Base64-encoded cryptographic proof of authentication.Pass as cavv
eciElectronic Commerce Indicator. Indicates the authentication level (e.g., "05" for Visa fully authenticated, "02" for Mastercard fully authenticated).Pass as eci
statusAuthentication status code. See table below.Used for your decision logic
dsTransIdDirectory Server Transaction ID. Used as the XID for 3DS 2.x transactions.Pass as xid
acsTransIdAccess Control Server transaction ID.Optional reference
protocolVersionThe 3DS protocol version used (e.g., "2.2.0", "2.1.0").Optional reference
scaIndicatorIndicates if Strong Customer Authentication (SCA) was applied.Optional reference

Example 3DS Response Object

Successful Authentication (Frictionless):

json
{
  "authenticationValue": "XYi1pplo2XITHfJdT21SweFz1us=",
  "eci": "05",
  "status": "Y",
  "protocolVersion": "2.2.0",
  "dsTransId": "d65e93c3-35ab-41ba-b307-767bfc19eae3",
  "acsTransId": "ca5f9649-b865-47ce-be6f-54422a0fce47",
  "scaIndicator": false
}

Failed Authentication:

json
{
  "eci": "07",
  "status": "N",
  "protocolVersion": "2.2.0",
  "dsTransId": "d65e93c3-35ab-41ba-b307-767bfc19eae3",
  "acsTransId": "65973509-34be-401c-8534-9712c089a938",
  "scaIndicator": false
}

3DS Status Codes

StatusMeaningLiability ShiftAction
YFully Authenticated — Cardholder identity verified.✅ YesProceed with transaction. Include 3DS data.
AAttempted — Authentication attempted but issuer doesn't fully support it.✅ Yes (typically)Proceed with transaction. Include 3DS data.
NNot Authenticated — Cardholder failed authentication.❌ NoConsider declining or proceeding without 3DS (at your own risk).
RRejected — Authentication rejected by the issuer.❌ NoDo not proceed. Inform the cardholder.
UUnavailable — Authentication could not be performed (technical issue).❌ NoProceed without 3DS or retry.
CChallenge Required — Cardholder was presented a challenge (handled by SDK).Depends on outcomeWait for final result after challenge completion.

Step 5: Submit to the gatewway API

After collecting the 3DS authentication data, send it along with the card details to our /api/transaction endpoint from your server.

Frontend: Send Data to Your Server

javascript
function submitPayment(threedsData) {
  var formData = {
    cardNumber: document.querySelector('[name=cardNumber]').value,
    cardMonth: document.querySelector('[name=cardMonth]').value,
    cardYear: document.querySelector('[name=cardYear]').value,
    cvv: document.querySelector('[name=cvv]').value,
    amount: 2500  // $25.00 in cents
  };

  // Include 3DS data if authentication was performed
  if (threedsData) {
    formData.threeds = {
      cavv: threedsData.authenticationValue,
      eci: threedsData.eci,
      xid: threedsData.dsTransId,
      status: threedsData.status,
      version: threedsData.protocolVersion
    };
  }

  fetch('/api/charge', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(formData)
  })
  .then(function(response) { return response.json(); })
  .then(function(data) {
    if (data.success) {
      window.location.href = '/thank-you';
    } else {
      alert('Payment failed: ' + data.message);
    }
  });
}

Backend: Call the Gateway Transaction API

Your server-side code takes the card data and 3DS values and submits them to gateway. The 3DS authentication values are included in the three_d_secure object within the transaction request.

bash
curl -X POST { https://sandbox.koipay.io }/api/transaction \
  -H "Authorization: YOUR_SECRET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "sale",
    "amount": 2500,
    "currency": "usd",
    "payment_method": {
      "card": {
        "card_number": "4111111111111111",
        "expiration_date": "12/25",
        "cvc": "123"
      }
    },
    "three_d_secure": {
      "cavv": "XYi1pplo2XITHfJdT21SweFz1us=",
      "eci": "05",
      "xid": "d65e93c3-35ab-41ba-b307-767bfc19eae3"
    }
  }'

Transaction Request Fields

FieldTypeRequiredDescription
typestringYes"sale" or "auth"
amountintegerYesTransaction amount in cents (e.g., 2500 = $25.00)
currencystringNoCurrency code (default: "usd")
payment_method.card.card_numberstringYesThe full card number
payment_method.card.expiration_datestringYesCard expiration in MM/YY format
payment_method.card.cvcstringNoCard verification code
three_d_secure.cavvstringYes*Cardholder Authentication Verification Value from PAAY
three_d_secure.ecistringYes*Electronic Commerce Indicator from PAAY
three_d_secure.xidstringYes*Transaction ID — use dsTransId from the PAAY response

* Required when submitting a 3DS-authenticated transaction. Omit the entire three_d_secure object if 3DS was not performed.

Note: You can also use a token from the Tokenizer in payment_method.token instead of raw card data. In that case, you would collect the card via Tokenizer, authenticate via PAAY separately, and then include both the token and 3DS data in your API call.


Complete Example

Here is a full working example with the PAAY JS SDK and manual verification:

html
<!DOCTYPE html>
<html>
<head>
  <title>Checkout with 3DS</title>
  <style>
    body { font-family: -apple-system, sans-serif; max-width: 480px; margin: 40px auto; }
    label { display: block; margin: 12px 0 4px; font-weight: 600; }
    input, select { padding: 8px; width: 100%; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px; }
    .row { display: flex; gap: 12px; }
    .row > div { flex: 1; }
    .pay-btn { margin-top: 20px; background: #0066cc; color: white; border: none; padding: 14px;
               border-radius: 6px; font-size: 16px; cursor: pointer; width: 100%; }
    .pay-btn:hover { background: #0052a3; }
    .pay-btn:disabled { background: #999; cursor: not-allowed; }
    .status { margin-top: 12px; padding: 10px; border-radius: 4px; display: none; }
    .status.info { display: block; background: #e8f4fd; color: #0066cc; }
    .status.error { display: block; background: #fde8e8; color: #cc0000; }
    .status.success { display: block; background: #e8fde8; color: #006600; }
  </style>
</head>
<body>
  <h1>Checkout — $25.00</h1>

  <form id="checkout-form">
    <label>Card Number</label>
    <input type="text" name="cardNumber" maxlength="19" placeholder="4111 1111 1111 1111"
           data-threeds="pan" autocomplete="cc-number" />

    <div class="row">
      <div>
        <label>Month</label>
        <select name="cardMonth" data-threeds="month">
          <option value="">--</option>
          <option value="01">01</option>
          <option value="02">02</option>
          <option value="03">03</option>
          <option value="04">04</option>
          <option value="05">05</option>
          <option value="06">06</option>
          <option value="07">07</option>
          <option value="08">08</option>
          <option value="09">09</option>
          <option value="10">10</option>
          <option value="11">11</option>
          <option value="12">12</option>
        </select>
      </div>
      <div>
        <label>Year</label>
        <select name="cardYear" data-threeds="year">
          <option value="">--</option>
          <option value="25">2025</option>
          <option value="26">2026</option>
          <option value="27">2027</option>
          <option value="28">2028</option>
          <option value="29">2029</option>
          <option value="30">2030</option>
        </select>
      </div>
      <div>
        <label>CVV</label>
        <input type="text" name="cvv" maxlength="4" placeholder="123" autocomplete="cc-csc" />
      </div>
    </div>

    <!-- Hidden 3DS fields -->
    <input type="hidden" name="x_transaction_id" value="" data-threeds="id" />
    <input type="hidden" name="paayAmount" value="25.00" data-threeds="amount" />

    <button type="button" class="pay-btn" id="pay-button">Pay $25.00</button>
    <div id="status" class="status"></div>
  </form>

  <!-- PAAY 3DS JS SDK -->
  <script src="https://cdn.3dsintegrator.com/threeds.min.latest.js"></script>

  <script>
    // Generate unique 3DS transaction ID
    var idInput = document.querySelector('[data-threeds=id]');
    idInput.value = 'id-' + Math.random().toString(36).substr(2, 16);

    // Initialize PAAY ThreeDS SDK
    var tds = new ThreeDS(
      'checkout-form',
      'YOUR_PAAY_API_KEY',          // Replace with your PAAY API key
      null,
      {
        autoSubmit: false,           // Manual control
        endpoint: 'https://api-sandbox.3dsintegrator.com/v2.2',
        verbose: true
      }
    );

    // Status display helper
    function showStatus(message, type) {
      var el = document.getElementById('status');
      el.textContent = message;
      el.className = 'status ' + type;
    }

    // Handle pay button click
    document.getElementById('pay-button').addEventListener('click', function() {
      var btn = this;
      btn.disabled = true;
      btn.textContent = 'Processing...';
      showStatus('Authenticating card...', 'info');

      var cardNumber = document.querySelector('[name=cardNumber]').value.replace(/\s/g, '');
      var firstDigit = cardNumber.charAt(0);

      // Only run 3DS for Visa (4) and Mastercard (5, 2)
      if (firstDigit === '4' || firstDigit === '5' || firstDigit === '2') {
        tds.verify({
          resolve: function(data) {
            console.log('3DS Result:', data);

            if (data.status === 'Y' || data.status === 'A') {
              showStatus('Authenticated! Processing payment...', 'success');
              submitToServer({
                cavv: data.authenticationValue,
                eci: data.eci,
                xid: data.dsTransId,
                status: data.status,
                version: data.protocolVersion
              });
            } else {
              showStatus('Authentication was not successful (status: ' + data.status + '). Payment not processed.', 'error');
              btn.disabled = false;
              btn.textContent = 'Pay $25.00';
            }
          },
          reject: function(error) {
            console.error('3DS Error:', error);
            showStatus('Authentication error. You may retry or proceed without 3DS.', 'error');
            btn.disabled = false;
            btn.textContent = 'Pay $25.00';
          }
        });
      } else {
        // Non-Visa/MC — proceed without 3DS
        showStatus('Processing payment...', 'info');
        submitToServer(null);
      }
    });

    function submitToServer(threedsData) {
      var payload = {
        cardNumber: document.querySelector('[name=cardNumber]').value.replace(/\s/g, ''),
        cardMonth: document.querySelector('[name=cardMonth]').value,
        cardYear: document.querySelector('[name=cardYear]').value,
        cvv: document.querySelector('[name=cvv]').value,
        amount: 2500
      };

      if (threedsData) {
        payload.threeds = threedsData;
      }

      fetch('/api/charge', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload)
      })
      .then(function(r) { return r.json(); })
      .then(function(data) {
        if (data.success) {
          showStatus('Payment successful!', 'success');
          setTimeout(function() { window.location.href = '/thank-you'; }, 1500);
        } else {
          showStatus('Payment failed: ' + (data.message || 'Unknown error'), 'error');
          document.getElementById('pay-button').disabled = false;
          document.getElementById('pay-button').textContent = 'Pay $25.00';
        }
      })
      .catch(function() {
        showStatus('Network error. Please try again.', 'error');
        document.getElementById('pay-button').disabled = false;
        document.getElementById('pay-button').textContent = 'Pay $25.00';
      });
    }
  </script>
</body>
</html>

Server-Side Example (Node.js)

Here's an example of your server receiving the frontend data and calling the Gateway API:

javascript
const express = require('express');
const fetch = require('node-fetch');
const app = express();

app.use(express.json());

const GW_API_KEY = 'your_secret_api_key';
const GW_URL = 'https://sandbox.koipay.io/api/transaction';

app.post('/api/charge', async (req, res) => {
  const { cardNumber, cardMonth, cardYear, cvv, amount, threeds } = req.body;

  // Build the transaction request
  const transactionRequest = {
    type: 'sale',
    amount: amount,
    currency: 'usd',
    payment_method: {
      card: {
        card_number: cardNumber,
        expiration_date: cardMonth + '/' + cardYear,
        cvc: cvv
      }
    }
  };

  // Include 3DS data if authentication was performed
  if (threeds && threeds.cavv) {
    transactionRequest.three_d_secure = {
      cavv: threeds.cavv,
      eci: threeds.eci,
      xid: threeds.xid
    };
  }

  try {
    const response = await fetch(GW_URL, {
      method: 'POST',
      headers: {
        'Authorization': GW_API_KEY,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(transactionRequest)
    });

    const data = await response.json();

    if (data.status === 'success') {
      res.json({ success: true, transactionId: data.data.id });
    } else {
      res.json({ success: false, message: data.msg });
    }
  } catch (error) {
    console.error('Transaction error:', error);
    res.json({ success: false, message: 'Internal server error' });
  }
});

app.listen(3000, () => console.log('Server running on port 3000'));

Using with Tokenizer Tokens

If you want the best of both worlds — PCI scope reduction via Tokenizer AND manual 3DS control via PAAY — you can combine both approaches:

  1. Use our Tokenizer to collect card data and receive a token (with paay.forceDisabled: true to skip Tokenizer's built-in 3DS).
  2. Use PAAY's JS SDK on a separate form with the card data to authenticate via 3DS.
  3. Send both the token and the 3DS values to your server.
  4. Submit the transaction with the token as the payment method and 3DS values as pass-through.
json
{
  "type": "sale",
  "amount": 2500,
  "payment_method": {
    "token": "the-token-from-tokenizer"
  },
  "three_d_secure": {
    "cavv": "XYi1pplo2XITHfJdT21SweFz1us=",
    "eci": "05",
    "xid": "d65e93c3-35ab-41ba-b307-767bfc19eae3"
  }
}

Note: This hybrid approach is advanced. In most cases, using the Tokenizer's built-in 3DS support is simpler and recommended.


ECI Values Reference

The ECI (Electronic Commerce Indicator) value indicates the level of authentication achieved. Commonly returned values:

ECIVisaMastercardMeaning
05Fully authenticated (Visa)
06Attempted authentication (Visa)
07Not authenticated / non-3DS (Visa)
02Fully authenticated (Mastercard)
01Attempted authentication (Mastercard)
00Not authenticated / non-3DS (Mastercard)

Testing

PAAY Sandbox

During development, point the SDK to PAAY's sandbox environment:

javascript
{
  endpoint: 'https://api-sandbox.3dsintegrator.com/v2.2',
  verbose: true
}

Gateway Sandbox

Use your sandbox API key and the sandbox URL for transaction processing:

https://sandbox.koipay.io/api/transaction

Combine with our test card numbers and PAAY sandbox test cards to simulate various 3DS outcomes.

Switching to Production

When going live, update both endpoints:

  1. PAAY SDK: Remove the endpoint option or set it to the production URL provided by PAAY.
  2. Gateway API: Switch from https://sandbox.koipay.io to your production gateway URL.
  3. API Keys: Replace sandbox keys with production keys for both PAAY and the gateway.
  4. Verbose: Set verbose: false to stop console logging.

Troubleshooting

IssueSolution
PAAY SDK not initializingVerify the form ID matches your <form> element's id attribute. Check the browser console for errors.
data-threeds inputs not detectedEnsure inputs with data-threeds attributes are inside the form element referenced by the SDK.
3DS authentication always returns UCheck that the card number is valid and the card issuer supports 3DS. In sandbox, use PAAY's test cards.
Challenge window not appearingEnsure the SDK script is loaded and the form is properly configured. Check verbose: true console output.
The gateway rejects 3DS valuesVerify that cavv, eci, and xid are passed correctly in the three_d_secure object. Values must be unaltered from the PAAY response.
tds.verify() not calling resolve/rejectEnsure card number, month, and year fields have valid values before calling verify.
Authentication works in sandbox but not productionConfirm you're using production API keys and endpoints for both PAAY and the gateway.

FAQ

What's the difference between using Tokenizer 3DS vs. PAAY JS SDK directly?

With Tokenizer's built-in 3DS, everything is handled inside the secure iframe — card collection, tokenization, and 3DS authentication. With the PAAY JS SDK approach, you manage the card form yourself and control when and how 3DS authentication occurs, then pass the results to our API. The Tokenizer approach is simpler; the PAAY SDK approach offers more flexibility.

Do I need a separate PAAY account?

Yes. PAAY provides the 3DS authentication service. Contact your gateway account representative to get set up with PAAY, or reach out to PAAY directly.

Does the PAAY JS SDK collect or see the full card number?

The PAAY SDK reads the card number, expiration month, and year from your form inputs (tagged with data-threeds attributes) to perform the 3DS authentication with the card issuer. The SDK communicates directly with PAAY's servers — the card data does not pass through your server during the 3DS authentication step.

Can I authenticate Amex and Discover cards?

PAAY supports Visa, Mastercard, American Express, and Discover. However, authentication rates vary by card brand and issuer. Check with PAAY for current support details.

What if 3DS authentication fails? Can I still process the transaction?

Yes, you can choose to process the transaction without 3DS data. However, you will not receive the liability shift benefit, meaning you remain liable for fraud-related chargebacks. Your business rules should determine whether to proceed.

Can I use this for recurring/subscription payments?

Yes. Authenticate the initial transaction with 3DS, then use the resulting credentials for subsequent recurring charges. PAAY's rebill option can also authenticate the rebill amount separately. See the Recurring API docs for setting up subscriptions.