3D Secure Integration Guide (MPI V4)

Integrate version 4 of the Segpay Gateway MPI for 3DS2 cardholder authentication, and migrate from the V2 integration.

Overview

The Gateway Merchant Plug-In (MPI) is Segpay's 3D Secure (3DS) authentication service. Version 2 is the current generation. It provides 3DS2 cardholder authentication for merchants who process payments through the Segpay Gateway and host their own pay pages.

Instead of integrating directly with a 3DS provider, you embed Segpay-provided components on your pay page. The components handle the authentication lifecycle: device data collection, issuer lookup, cardholder challenge when required, and delivery of a signed authentication result.

V4 replaces the shared-secret JSON Web Token (JWT) model of the previous version with asymmetric key signing, and moves all card data handling into an isolated component served from the Gateway's origin. These changes eliminate JWT reuse attacks and reduce your PCI DSS (Payment Card Industry Data Security Standard) compliance scope.

Both versions currently run in parallel. Your existing V2 integration continues to work, but Segpay plans to deprecate V2. New integrations should always use V4. If you have an existing V2 integration, use this guide to plan your migration.

Why migrate

In short: V4 is more secure than V2, reduces your compliance burden, and V2 is going away.

  • Card data never enters your systems. The MPI core captures card data inside an iframe served from the Gateway's origin. Your page and backend handle only a one-way hash of the card number, reducing your PCI DSS scope.

  • No shared secrets. In V2, a leaked shared key lets an attacker forge authentication tokens. In V4, you sign with a private key that never leaves your systems; there is nothing to transmit or leak.

  • Signing requests cannot be redirected. Your signing endpoint is registered with Segpay at onboarding and delivered inside a Segpay-signed token. Malicious script on your pay page cannot reroute signing requests elsewhere.

  • Script integrity is verified. The browser checks each Segpay-provided script against a cryptographic fingerprint before running it and refuses to run anything that has been altered. This helps you meet PCI DSS v4.0 requirement 6.4.3 for payment page scripts.

  • V2 will be deprecated. Migrating now lets you plan the work on your schedule instead of against a deprecation deadline.

What’s changed

This is a completely new integration, not an update to your existing one. The table below maps each element of the current integration to the new integration equivalent.

Integration element Current integration (V2) New integration (V4)
JWT signing Shared secret key (secureKey) issued by Segpay, HS256 Your own EC P-256 key pair, ES256. No shared secrets.
Key exchange Secret delivered at onboarding by Segpay Support. You publish public keys at a JWKS URL. Segpay fetches them automatically.
Page components Secure3D.js loaded directly into your pay page. An MPI iframe served from the Gateway's origin, plus a thin host script, both with SRI integrity verification.

Card data handling

Your page passes card data to the library in the order object. The MPI core inside the iframe captures card data. Your page and backend handle only a one-way card hash.
JWT creation Your server generates a JWT before the pay page loads. The MPI calls your registered signing endpoint during the flow to obtain signed order JWTs.
Signing endpoint Not applicable. Registered at onboarding. Cannot be redirected at runtime.
Library initialization Secure.setup() with merchant code and JWT. GatewayMPI.init() with callback registration.
Card number detection Secure.enableBinDetection() with a field ID. Your page calls GatewayMPI.onCardNumberChanged() when card entry is complete.
Authentication trigger Your page calls Secure.do3D(order) on submit. The MPI intercepts submission through the onSubmitPayment callback.
Authentication results payments.validated and payments.noAction events. onAuthenticationSuccess and onAuthenticationFailure callbacks.
Replay protection Unique order numbers required to prevent reuse of intercepted tokens. Signed, short-lived JWTs bound to the order and card hash. The Gateway recomputes the hash and rejects any mismatch.
Authorization handoff ThreeDService, ThreeDSecure (Base64 data object), SecureTransactionID in the authorization request. Unchanged: same fields. Read the values from the onAuthenticationSuccess result. See Complete Authorization.
Merchant-initiated transactions SecureTransactionID and SecureGrandFathered parameters SecureTransactionID, unchanged. SecureGrandFathered is obsolete: card schemes ended support in late 2025.
Issuer-mandated authentication On a 3009 response, call Secure.forceAuthentication(order) Call GatewayMPI.forceAuthentication() to re-run the authentication with a mandated challenge. See Force a step-up challenge.
Lookup interception Optional callback on Secure.do3D(), resume with Secure.continue() onLookup callback, notification only. Conditional continue and abort are not supported in V4.
Billing address Required in the order object (Address1, City, PostalCode) Optional getBillingData retriever supplies billing details for the issuer's risk assessment. Strongly recommended. See Billing data.

Prerequisites

  • Gateway account enabled for MPI V4. Contact Gateway Support to request this: gatewaysupport@segpay.com

  • Complete onboarding. Necessary for the MPI to function. Register your pay page origins, JWKS URL, and JWT signing endpoint. See Complete Onboarding.

  • Serve all pay pages over HTTPS. The MPI core requires a secure context. HTTP origins are rejected at registration.

  • Generate and store an EC P-256 key pair. Your private key must be stored securely, for example in a secrets manager or HSM. See Manage signing keys.

  • Host a JWT signing endpoint on your backend. The MPI calls this endpoint during the authentication flow. See Integrate your backend.

  • Host or reference a JWKS endpoint on your backend. Segpay verifies your signed JWTs against the public keys you publish there.

How the integration works

The integration has two components that you place on your pay page:

  • MPI host script. A small script that runs in your page and relays messages between your page and the MPI iframe. It exposes the GatewayMPI API and registers your callbacks. It holds no sensitive state and makes no Gateway API calls directly.

  • MPI iframe. The MPI core, served from the Gateway's own origin in an isolated browsing context. All Gateway API communication, card hash computation, Auth JWT handling, and session state live here. Scripts on your page cannot read or modify anything inside the iframe: its content, internal state, or network traffic.

During a typical authentication:

  1. The cardholder completes card entry, and your page notifies the MPI.

  2. The MPI core begins device data collection in the background and calls your signing endpoint to obtain a signed order JWT.

  3. The cardholder submits the order. The MPI intercepts submission, calls your signing endpoint for a signed lookup JWT, and performs the issuer lookup.

  4. If the issuer requires a challenge, the MPI presents it to the cardholder.

  5. The MPI returns the authentication result to your page through the onAuthenticationSuccess callback, or onAuthenticationFailure if authentication did not pass.

  6. You submit the payment to the Segpay Gateway for authorization. See Complete authorization.

Complete onboarding

Before you begin your integration, contact Gateway Support (gatewaysupport@segpay.com) to register your allowed origins, JWKS URL, and JWT signing details.

1. Register your allowed origins

Register every origin from which your pages will embed the MPI iframe. An origin is the scheme (the https:// part) plus the hostname:

https://checkout.example.com
https://www.example.com
https://pay.example.com
https://*.example.com

The Gateway validates registered origins in two places:

  • The HTTP Origin header when your page requests the MPI iframe.

  • event.origin on all messages between the host script and the iframe.

Only registered origins can embed the iframe and communicate with the MPI core.

Wildcard subdomains are supported

  • A pattern such as https://*.example.com matches any subdomain depth (checkout.example.com, eu.checkout.example.com) but does not match the apex domain example.com or similarly named hosts (example.com.attacker.com).

  • The * must replace the entire first segment of the hostname. Patterns that wildcard part of a segment (https://foo*.example.com) or the whole domain (https://*.com) are rejected at registration..

Register only https:// origins. The MPI core requires a secure context to access the Web Crypto API used for card hash computation. HTTP origins are rejected.

2. Register your JWKS URL

Your backend signs JWTs that the Gateway verifies using public keys you publish at a JWKS endpoint.

  • Provide a URL to a publicly accessible JWKS endpoint. The Gateway fetches and caches this endpoint hourly.

  • The endpoint must return a valid JWK Set (RFC 7517), and every key must include a unique kid (key ID) value.

  • Key rotation is transparent: publish the new key at your JWKS endpoint and the Gateway picks it up within the hour.

  • Keys must be EC keys using the P-256 curve (ES256). RSA keys are not supported.

3. Register your JWT signing endpoint

Register the URL of the backend endpoint the MPI core calls to obtain signed JWTs. The Gateway stores this URL and delivers it to the MPI inside the Gateway-signed Auth JWT. It cannot be overridden at runtime by your pay page.

  • The endpoint must be served over HTTPS and accept POST requests as described in Integrate your backend.

  • The endpoint must also permit cross-origin requests from the Gateway's origin, because the MPI core calls it from inside the iframe. Add the following CORS response header:

Access-Control-Allow-Origin: https://secure3d.segpay.com

Use the same domain for 3DS that you use for payment processing. For Segpay Gateway merchants, the Gateway origin is https://secure3d.segpay.com.

What you receive

Item Description
MPI Key Public identifier for your Gateway MPI integration. Embedded in your pay page HTML. Not a secret; it is visible in page source.
Gateway JWKS URL https://secure3d.segpay.com/.well-known/jwks.json. Used to verify Gateway-signed JWTs.
Host script URL and SRI hash The versioned script URL and its integrity hash for your pay page.

Integrate your pay page

Your pay page embeds the MPI iframe, loads the host script, registers callbacks, and notifies the library when the card number changes. The MPI core inside the iframe handles card data capture and the 3DS2 flow.

1. Embed the MPI iframe and host script

Add both elements to your pay page. The iframe must appear before the host script.

<!-- 1. MPI iframe: your MPI Key is embedded in the src URL -->
<iframe
  id="gateway-mpi-frame"
  src="https://secure3d.segpay.com/mpi-frame/v4?key=mpk_live_abc123xyz"
  style="display:none">
</iframe>

<!-- 2. MPI host script: versioned URL and SRI hash are mandatory -->
<script
  src="https://secure3d.segpay.com/js/v4.0.1/mpi-host.js"
  integrity="sha384-<hash-provided-by-gateway>"
  crossorigin="anonymous">
</script>

The MPI Key issued during onboarding is embedded in the iframe src URL as the key query parameter. It does not need to appear separately in your page JavaScript. It is a public identifier and may appear in page source.

Both Segpay-provided scripts carry Subresource Integrity (SRI) hashes: cryptographic fingerprints the browser checks before running each script. A script that has been modified in transit will not run. The iframe's script is generated fresh for each request with your registered origins built in and fingerprinted, so the origins list cannot be tampered with either. You remain responsible for the script inventory of all other scripts on your pay page (PCI DSS v4.0, requirement 6.4.3).

2. Register callbacks

Initialize the library by calling GatewayMPI.init() with your callback functions. The library calls these functions to retrieve data and to notify your page of events.

GatewayMPI.init({
  // Value retrievers: called by the MPI when it needs data
  getCardNumber:       () => /* return raw card number string */,
  getExpiryMonth:      () => /* return 2-digit month string, e.g. '09' */,
  getExpiryYear:       () => /* return 4-digit year string, e.g. '2027' */,
  getMerchantOrderNum: () => /* return your internal order reference */,
  getAmount:           () => /* return the amount to be charged */,
  getCurrencyCode:     () => /* return the 3-character ISO currency code */,

  // UI callbacks: EMVCo guidelines require a loading indicator
  // at defined points during authentication
  onShowLoading: () => { /* show spinner, disable submit button */ },
  onHideLoading: () => { /* hide spinner, re-enable submit button */ },

  // Submit interception
  onSubmitPayment: (trigger) => {
    // Run your own client-side validation, then call trigger() to proceed
    myValidation().then(() => trigger());
  },

  // Result and event callbacks
  onInitialization:        (authJWT) => { /* optional: Gateway Auth JWT is available */ },
  onLookup:                (result) => { /* optional: lookup result, notification only */ },
  onAuthenticationSuccess: (result) => { /* 3DS passed: proceed with payment */ },
  onAuthenticationFailure: (result) => { /* 3DS did not pass: decide retry or abandon */ },
  onAuthenticationError:   (error)  => { /* { errorCode, message }: flow could not complete */ },
  onMPIReset:              ()       => { /* library has reset */ },

  // Optional settings
  RequireSuccessful3D: false,  // when true, resets automatically after a failed authentication
});

If you have no pre-submit validation, the simplest onSubmitPayment implementation is:

onSubmitPayment: (trigger) => trigger(),

The V4 library does not use the CVV. Collect it on your form for authorization as usual; the MPI does not read it. The library also supports a RequireSuccessful3D option, set at init(): when enabled, the library resets automatically after a failed authentication so the cardholder's next submit can retry.

Billing data (strongly recommended)

The optional getBillingData retriever supplies the cardholder's billing details. The Gateway forwards them to the issuer as part of the 3DS2 authentication, where they feed the issuer's risk assessment. Supplying them significantly improves the odds of a frictionless authentication (no cardholder challenge); omitting them makes step-up challenges more likely.

Like the other retrievers, getBillingData is called at the moment the library needs the data, not at initialization:

getBillingData: () => ({
  firstName:   'Jane',            // or supply fullName instead
  lastName:    'Doe',
  fullName:    '',                // alternative to firstName + lastName
  address1:    '123 Main St',
  address2:    '',
  address3:    '',
  city:        'Springfield',
  state:       'IL',
  postalCode:  '62701',
  countryCode: 'US',              // ISO 3166 country code
  phone:       '+15551234567',
  email:       'jane@example.com',
}),
  • Every field is optional, and every value is a string.

  • Provide fullName or the firstName and lastName pair; if both are present, fullName is used.

  • Omit fields you do not collect, or return them as empty strings; they are simply not sent.

  • The Gateway validates and normalizes all values before forwarding them.

  • An unrecognized countryCode causes that field to be skipped rather than failing the authentication.

Send everything you collect on the payment page: at minimum name, address1, city, postalCode, countryCode, and email or phone. The issuer's risk engine weighs these fields when deciding whether to challenge the cardholder.

Value retriever callbacks

The library calls the getter callbacks at the moment it needs the data, not at initialization. Two patterns are supported.

DOM selector pattern
getCardNumber: () => document.querySelector('#card-number').value,
Framework state pattern (React, Vue, Angular)
getCardNumber: () => this.state.cardNumber,   // React class component
getCardNumber: () => cardNumber.value,        // Vue ref
getCardNumber: () => myFormGroup.value.card,  // Angular reactive form

The library strips non-numeric characters from the card number automatically. Spaces, dashes, and other display formatting do not need to be removed in the getter.

Return the expiry month and year separately, regardless of how your form stores them:

// From a combined 'MM/YY' field
getExpiryMonth: () => expiryField.value.split('/')[0],
getExpiryYear:  () => expiryField.value.split('/')[1],
// From separate select elements
getExpiryMonth: () => document.querySelector('#exp-month').value,
getExpiryYear:  () => document.querySelector('#exp-year').value,

3. Notify the library of card number changes

The MPI starts 3DS2 device data collection as soon as a complete card number is available. Collection runs in the background while the cardholder fills in the remaining fields, which reduces latency at submission.

To enable, call GatewayMPI.onCardNumberChanged(value) when the card number field is fully entered.

Do not raise this event on every keystroke. Raise it only when the card number is complete: on field blur, when a masked input reaches maximum length, or when a card formatter confirms a valid entry.

// Example: raise on field blur
cardNumberInput.addEventListener('blur', () => {
  GatewayMPI.onCardNumberChanged(cardNumberInput.value);
});

// Example: raise when the digit count reaches a full card length
cardNumberInput.addEventListener('input', () => {
  const digits = cardNumberInput.value.replace(/\D/g, '');
  if (digits.length >= 15) {  // 15 for Amex, 16 for Visa/MC/Discover
    GatewayMPI.onCardNumberChanged(digits);
  }
});

If the cardholder changes the card number after device data collection has run, the library resets its session state and restarts collection automatically. Continue calling onCardNumberChanged whenever the value changes; your page does not need to handle the reset.

4. Receive the authentication result

On successful authentication, the library calls onAuthenticationSuccess with a result object.

  • Pass the relevant values to your backend for payment processing.

  • If authentication ran but did not pass (cancellation, denial, or timeout), the library calls onAuthenticationFailure with the same result shape instead; decide whether to let the cardholder retry or abandon the purchase.

onAuthenticationSuccess: (result) => {
  submitPaymentToBackend({
    threeDSVersion:       result.threeDSVersion,
    enrolled:             result.enrolled,
    authenticationId:     result.authenticationId,
    cavv:                 result.cavv,
    eci:                  result.eci,
    authenticationStatus: result.authenticationStatus,
    signedJWT:            result.signedJWT,      // optional: verify server-side
  });
},

The result fields map one-to-one to the V2 result data; only the field names changed to camelCase (V2's PAResStatus is now authenticationStatus, EciFlag is now eci).

Field Values
enrolled Y enrolled · N not enrolled · U enrollment unavailable
authenticationStatus Y authenticated · N failed · U unavailable · A attempted · R rejected
eci Visa 05 / Mastercard 02 authenticated; Visa 06 / Mastercard 01 attempted; 07 / 00 not authenticated
cavv Base64-encoded cardholder authentication value.
cavvAlgorithm Provider algorithm indicator.
threeDSVersion 2.1.0, 2.2.0
signatureVerification Y / N
authenticationId, dsTransactionId, acsTransactionId, threeDSServerTransactionId Provider-issued transaction identifiers.
statusReason Additional detail on the authentication outcome.
mpiVersion Version of the MPI that produced the result.

After a failure: the library keeps its session state so you can inspect the result.

  • To let the cardholder try again, call GatewayMPI.reset(); the library tears down the session and starts fresh (see MPI reset).

  • Alternatively, set RequireSuccessful3D: true at init() and the library resets itself automatically after every failed authentication.

The result is also delivered as a JWT signed by the Gateway, verifiable against https://secure3d.segpay.com/.well-known/jwks.json. To guard against frontend result tampering, pass signedJWT to your backend and verify it before fulfilling the order. Verification is optional; the risk of skipping it is yours to accept.

5. Force a step-up challenge

By default, the issuer decides whether the cardholder is challenged, and most authentications complete frictionless, with no cardholder interaction. GatewayMPI.forceAuthentication() requests that the issuer mandate a challenge on the next authentication.

GatewayMPI.forceAuthentication();

The method takes no arguments and returns immediately; it arms the library rather than running anything. The forced state is sticky: it persists across authentications until GatewayMPI.reset() is called. It can only raise friction; there is no way to request less authentication than the issuer would apply on its own.

There are two situations where you need it:

  • Always-force. Your risk policy requires strong authentication on every transaction. Call forceAuthentication() once, immediately after GatewayMPI.init(). Every authentication in the session is then challenge-mandated.

  • Response code 3009 recovery. A frictionless authentication succeeded, but the subsequent authorization returned response code 3009 (Strong Authentication Required): the issuer wants a challenge before it will approve the payment. Re-run the authentication in forced mode, then resubmit the authorization. If your checkout re-renders the payment page after a declined authorization, render the page in forced mode by calling forceAuthentication() right after init(), exactly like the always-force case. If your checkout stays on the same page, call forceAuthentication() when you see the 3009. The library re-arms itself and re-runs device data collection in the background; the cardholder does not re-enter their card. The next payment submission runs the forced authentication, and the cardholder is presented with the challenge.

Integrate your backend

Your backend has two responsibilities:

  • Implement the JWT signing endpoint that the MPI calls during the flow.

  • Verify the Auth JWT presented with each request.

The PHP examples below use the firebase/php-jwt library. Install it with Composer, PHP's dependency manager:

composer require firebase/php-jwt

The JWT signing endpoint

This is the URL you registered during onboarding. The MPI calls it twice during a normal authentication:

  • Order JWT request. Triggered when the cardholder completes card entry. The MPI computes the card hash and includes it in the request body. Your endpoint embeds the hash as-is alongside your order details and returns a signed order JWT.

  • Lookup JWT request. Triggered when the cardholder submits the order. The MPI again includes the card hash. Your endpoint embeds it as-is and returns a signed lookup JWT for the issuer lookup.

Request format (both calls)

POST /your/registered/signing/endpoint
Content-Type: application/json
Authorization: Bearer &lt;mpi-auth-jwt&gt;

{
  "merchantOrderNum": "ORD-20260311-9182",
  "cardHash": "a3f8c2d1..."
}

1. Verify the Auth JWT

Every request from the MPI presents the Gateway-signed Auth JWT as a Bearer token. Verify it before processing the request:

  1. Fetch the Gateway JWKS from https://secure3d.segpay.com/.well-known/jwks.json and cache it. Do not fetch on every request. Cache for at least 60 minutes and refresh only when verification fails with an unknown kid.

  2. Read the kid from the JWT header and locate the matching key in the cached JWKS.

  3. Verify the JWT signature using ES256 and that key.

  4. Verify the JWT exp claim has not passed.

  5. Verify the merchantId claim matches your own merchant ID.

PHP example

<?php
use Firebase\JWT\JWT;
use Firebase\JWT\JWK;

define('MY_MERCHANT_ID', 'your-merchant-id');  // the merchant identifier issued by Gateway Support
define('GATEWAY_JWKS_URL', 'https://secure3d.segpay.com/.well-known/jwks.json');
define('JWKS_CACHE_FILE', sys_get_temp_dir() . '/gateway_jwks_cache.json');
define('JWKS_CACHE_TTL', 3600); // seconds: refresh hourly

function getGatewayJWKS(): array {
    if (file_exists(JWKS_CACHE_FILE)) {
        $cached = json_decode(file_get_contents(JWKS_CACHE_FILE), true);
        if (time() - $cached['fetched_at'] < JWKS_CACHE_TTL) {
            return $cached['keys'];
        }
    }
    $json = file_get_contents(GATEWAY_JWKS_URL);
    $jwks = json_decode($json, true);
    file_put_contents(JWKS_CACHE_FILE, json_encode([
        'fetched_at' => time(),
        'keys'       => $jwks,
    ]));
    return $jwks;
}

function verifyAuthJWT(string $bearerToken): object {
    $jwks    = getGatewayJWKS();
    $keySet  = JWK::parseKeySet($jwks);
    $payload = JWT::decode($bearerToken, $keySet);

    if ($payload->merchantId !== MY_MERCHANT_ID) {
        throw new RuntimeException('merchantId mismatch');
    }
    return $payload;
}

2. Build and sign the order JWT

After verifying the Auth JWT, look up the canonical order details from your own database using merchantOrderNum, embed the cardHash from the request body, and sign.

Always look up order details from your own database. Never trust order amounts, currency, or other details supplied in the request body. The signed JWT is your attestation that these values are correct according to your authoritative records.

Order JWT payload (JSON)

{
  "merchantId":       "your-merchant-id",
  "merchantOrderNum": "ORD-20260311-9182",
  "amount":           "49.99",
  "currency":         "USD",
  "cardHash":         "a3f8c2d1..."
}

Signing and responding (PHP)

&lt;?php
use Firebase\JWT\JWT;

function signJWT(array $payload, string $kid): string {
    // Load the private key from an environment variable or file. Never hardcode it.
    $privateKeyPem = getenv('MERCHANT_PRIVATE_KEY')
        ?: file_get_contents('/path/to/merchant-private.pem');

    return JWT::encode($payload, $privateKeyPem, 'ES256', $kid);
}

// Endpoint handler
$body       = json_decode(file_get_contents('php://input'), true);
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
$token      = str_replace('Bearer ', '', $authHeader);

// Step 1: verify the Gateway-signed Auth JWT
$authPayload = verifyAuthJWT($token);

// Step 2: look up order details from YOUR database
$order = getOrderFromDB($body['merchantOrderNum']);
if (!$order) {
    http_response_code(404);
    exit;
}

// Step 3: build and sign the order JWT
$payload = [
    'merchantId'       =&gt; MY_MERCHANT_ID,
    'merchantOrderNum' =&gt; $order['order_num'],
    'amount'           =&gt; $order['amount'],
    'currency'         =&gt; $order['currency'],
    'cardHash'         =&gt; $body['cardHash'], // embed as-is from the request
    'iat'              =&gt; time(),
    'exp'              =&gt; time() + 300,      // 5-minute expiry
];

$jwt = signJWT($payload, 'my-key-id-v1');

header('Content-Type: application/json');
echo json_encode(['orderJWT' =&gt; $jwt]);

The kid passed to JWT::encode() must match one of the key IDs published at your JWKS endpoint. If you have two active keys during rotation, use the kid corresponding to the private key you are currently signing with.

3. Build and sign the lookup JWT

The lookup JWT has the same structure and signing process as the order JWT. Look up the order from your database, embed the cardHash from the request body, and sign with the same private key and kid. If a key rotation occurred between the two calls, use the new kid.

4. Add advanced settings on the lookup JWT

The lookup JWT accepts an optional settings object carrying additional EMV 3DS fields that describe the transaction to the issuer. Because these change how the issuer treats the authentication, they only take effect when your backend signs them: the Gateway honors settings only from the verified lookup JWT and ignores any supplied as unsigned request parameters.

Your signing endpoint is the trust boundary. Derive these values from your own order and account records before signing them. Page JavaScript can propose settings by passing them to your signing endpoint, but nothing takes effect unless you sign it, so do not blindly sign a settings object handed to your endpoint by the browser.

Keys are camelCase, and every value is a string.

{
  "merchantId":       "your-merchant-id",
  "merchantOrderNum": "ORD-20260311-9182",
  "amount":           "49.99",
  "currency":         "USD",
  "cardHash":         "a3f8c2d1...",
  "settings": {
    "authenticationIndicator": "01",
    "messageCategory":         "01"
  }
}

The two most common settings are the authentication-policy indicators.

authenticationIndicator describes the type of authentication being requested. It defaults to 01 when omitted.

Value Meaning
01 Payment transaction (default).
02 Recurring transaction.
03 Installment transaction.
04 Add card.
05 Maintain card.
06 Cardholder verification as part of EMV token ID&V.

messageCategory describes whether the authentication accompanies a payment. It is omitted by default.

Value Meaning
01 PA: payment authentication.
02 NPA: non-payment authentication.

Set these from your own order records: for example, authenticationIndicator: "04" with messageCategory: "02" when a card is being added to an account without a purchase. A lookup carrying an unrecognized value in either of these two indicators is rejected outright.

Recurring and installment. authenticationIndicator values 02 (recurring) and 03 (installment) require companion settings describing the payment schedule, or the issuer rejects the lookup:

Setting Required for Meaning
recurringFrequency 02, 03 Minimum number of days between authorizations, for example 30.
recurringEnd 02, 03 Last date on which authorizations may be made, YYYYMMDD.
installment 03 Maximum number of authorizations permitted for the installment payments.

Example: a monthly subscription signup ending December 31, 2027.

{
  "merchantId":       "your-merchant-id",
  "merchantOrderNum": "ORD-20260311-9182",
  "amount":           "49.99",
  "currency":         "USD",
  "cardHash":         "a3f8c2d1...",
  "settings": {
    "authenticationIndicator": "02",
    "recurringFrequency":      "30",
    "recurringEnd":            "20271231"
  }
}

Any other EMV 3DS field the Gateway supports (shipping details, cardholder account-age risk signals, token fields, and so on) may be supplied in the same settings object.

Validation differences

Validation differs between the two indicators above and everything else: the two indicators are value-checked, and a bad value fails the lookup.

Unrecognized and malformed values

For the rest, an unrecognized key is silently ignored, and a recognized key carrying a malformed value is forwarded as-is and may be rejected by the MPI provider.

Protected fields

A small set of protected signing and identity fields can never be overridden.

Contact Gateway Support for the full list of supported settings and guidance on applying them to your integration.

Manage signing keys

Generate an EC key pair

Signing keys must be EC (elliptic curve) keys on the P-256 curve. Generate a key pair with OpenSSL:

# Generate the private key
openssl ecparam -name prime256v1 -genkey -noout -out merchant-private.pem

# Derive the public key
openssl ec -in merchant-private.pem -pubout -out merchant-public.pem
  • Publish the public key at your JWKS endpoint. A JWKS endpoint is any HTTPS URL you host that returns your public keys as JSON in JWK format. Most JWT libraries, including firebase/php-jwt, can convert a PEM public key to JWK values, and the endpoint itself can be a static file or a simple route that returns the JSON shown in Rotate keys with zero downtime.

  • Store merchant-private.pem in a secrets manager, HSM, or environment variable. Never commit it to source control or expose it in any client-facing context.

Rotate keys with zero downtime

The Gateway fetches your key set automatically, and all keys returned by your JWKS endpoint are usable at all times.

  1. Generate a new key pair with a new kid value.

  2. Publish both keys at your JWKS endpoint: the existing key and the new key. Your endpoint must return both simultaneously during the overlap window.

    {
          "keys": [
            {
              "kty": "EC",
              "crv": "P-256",
              "kid": "my-key-id-v1",
              "use": "sig",
              "x": "<existing-key-x>",
              "y": "<existing-key-y>"
            },
            {
              "kty": "EC",
              "crv": "P-256",
              "kid": "my-key-id-v2",
              "use": "sig",
              "x": "<new-key-x>",
              "y": "<new-key-y>"
            }
          ]
        }
  3. Wait up to one hour for the Gateway to refresh its cache of your JWKS endpoint. You can begin signing with the new key immediately; the Gateway accepts both kid values once the cache refreshes.

  4. Update your signing endpoint to sign new JWTs with the new private key and new kid.

  5. Allow an overlap window of at least one hour after step 4 so all in-flight sessions signed with the old kid complete.

  6. Remove the old key from your JWKS endpoint.

If verification fails due to an unknown kid, for example before the hourly cache refresh picks up your new key, the Gateway attempts a fresh fetch of your JWKS URL before rejecting the JWT. In practice the transition is seamless, but the one-hour overlap in step 5 remains the safe minimum before removing the old key.

Error handling

Named error codes

The library calls onAuthenticationError with an { errorCode, message } object when the flow cannot continue. Present an appropriate message to the cardholder for each case.

Error code Cause Recommended response
InvalidKey MPI Key or HTTP origin not recognized. Verify the MPI Key value in the page and that the page is served from a registered origin. Contact Gateway Support if the origin is correctly registered.
InvalidOrderJWT Order JWT signature invalid or kid unknown. Verify your backend is signing with the correct private key and that the kid matches a published key.
CardDataTampered Card hash in the JWT does not match the card data the Gateway received. Indicates possible data manipulation in transit. Present a generic error to the cardholder and log the event for investigation.
VelocityExceeded Too many authentication attempts for this order or card. Inform the cardholder the attempt limit has been reached. Advise them to contact support if the issue persists.
InvalidCard The MPI provider rejected the card during device data exchange. Ask the cardholder to check the card number and retry.
InvalidJWT Lookup JWT signature invalid or kid unknown. Same remediation as InvalidOrderJWT.
ChallengeIncomplete The 3DS2 cardholder challenge timed out or was abandoned. Ask the cardholder to retry. If the issue persists, advise them to contact their card issuer.
DataMismatch The transaction ID could not be matched to an expected issuer record. Contact Gateway Support if this error occurs consistently.
InternalError A request could not be completed: network failure or an unexpected internal error. Ask the cardholder to retry. Contact Gateway Support if the error persists.
UpstreamError The Gateway MPI API returned an unexpected error. Ask the cardholder to retry. Contact Gateway Support if the error persists.

MPI reset

If an API call fails in a way that cannot be recovered within the current session, the library performs a full internal reset and calls onMPIReset. The library returns to its initial state, and device data collection restarts on the next valid onCardNumberChanged event. If the RequireSuccessful3D option is enabled, the library also resets automatically after a failed authentication so the next submit can retry.

onMPIReset: () => {
  // Recommended: fetch a fresh order number from your backend
  fetchNewOrderNumber().then(newOrderNum => {
    updateOrderReference(newOrderNum);
  });

  // Always: present a user-friendly message
  showMessage('Something went wrong. Please re-enter your card details and try again.');
},

Obtain a new order number on reset. Reusing the same order number after a reset may cause velocity check failures on subsequent authentication attempts.

Correlate issues with Gateway Support

Every Gateway MPI API response carries an X-TraceId response header that uniquely identifies the request. When reporting an issue, capture the X-TraceId value from the failing request (visible in the browser's network inspector on the calls made by the MPI iframe, or in an exported HAR file) and include it in your report. It lets support locate the exact request, its logs, and the corresponding MPI provider calls immediately.

Complete authorization

The authorization step is unchanged from V2. Pass the authentication result to the Segpay Gateway in the same three fields documented in the Gateway Integration Guide.

Field Value
ThreeDService CARDINAL
ThreeDSecure The Base64-encoded authentication result.
SecureTransactionID The authentication transaction identifier. Required when a merchant-initiated transaction references the original authenticated transaction.

The only change from V2 is the source of the values: read them from the onAuthenticationSuccess result instead of the V2 payments.validated event. The SCA exemption parameters (SCAExemption and SCAExemptionOverride) are unchanged.

For recurring and other merchant-initiated transactions, reference the original authenticated transaction with SecureTransactionID. To flag the authentication itself as recurring or installment, sign the corresponding advanced settings on the lookup JWT; see Add advanced settings on the lookup JWT. The SecureGrandFathered parameter is obsolete: card schemes ended support for grandfathered transactions in late 2025, and affected subscriptions were re-prompted for authentication from that date.

Test your integration

End-to-end testing is coordinated through Gateway Support. Contact the team to schedule integration testing: gatewaysupport@segpay.com

Integration checklist

Use this checklist before requesting production activation.

Onboarding

  • All pay page origins registered (HTTPS only)

  • JWKS URL registered

  • JWT signing endpoint URL registered

Frontend

  • MPI iframe embedded with MPI Key in the src URL (HTTPS only)

  • MPI host script loaded with versioned URL and SRI hash attribute

  • All callbacks registered via GatewayMPI.init()

  • getBillingData implemented with the billing fields collected on the page

  • onCardNumberChanged raised on complete card entry, not on every keystroke

  • onShowLoading and onHideLoading implemented to prevent duplicate submissions

  • onAuthenticationFailure handled: reset for retry, or RequireSuccessful3D enabled

  • Response code 3009 handled via forceAuthentication() (see Force a step-up challenge)

  • onMPIReset handler implemented with a new order number fetch

Backend

  • JWT signing endpoint permits CORS from the Gateway origin

  • JWT signing endpoint verifies the Auth JWT before signing

  • Order details looked up from your database, not trusted from the request body

  • Order JWT and lookup JWT signed with ES256 and the correct kid

  • Gateway JWKS cached, not fetched on every request

  • Private key stored securely, not in source control

Key management

  • EC P-256 key pair generated and public key published at your JWKS endpoint

  • Key rotation procedure documented and tested

Testing

End-to-end integration testing completed with Gateway Support: gatewaysupport@segpay.com

See also