Accept online payments with Hosted Payment Elements

Build a secure checkout quickly with NCR Voyix Hosted Payment Elements (HPE). Embed individual hosted payment fields directly in your page, support card payments, and keep card data outside your systems.

HPE is a client-side SDK that renders each payment field in its own <iframe>. Your backend creates a session, your frontend collects card data through the hosted fields, and your backend finalizes the payment after the form is submitted.

How HPE works

  1. Load the SDK in your page and call ncr.elements() to instantiate the hosted payment form.
  2. Configure payment fields — each field (card number, expiry, CVV, etc.) renders in its own secured iframe hosted by NCR.
  3. Create session on your server with InitializeSession and receive the session details needed to submit.
  4. Submit the form by calling paymentForm.submit(submissionConfig) with the session details.
Why teams choose HPE

Full UI control with PCI-safe hosted fields, granular masking and styling per field, and a consistent payment flow managed by NCR Voyix.

Quickstart (first successful payment)

Follow this order to get to your first successful checkout with minimal rework.

  1. Generate RSA keys and keep private key material secure. See RSA key generation.
  2. Validate with Postman first so signing and headers are confirmed before app coding. See Postman end-to-end.
  3. Load the HPE SDK and call ncr.elements() to create the payment form with hosted fields.
  4. Implement the submit flow: InitializeSession → paymentForm.submit() → CompleteSession.
  5. Verify success criteria: receive statusCode = 100 and then call CompleteSession once.
First success criteria

Your setup is healthy when InitializeSession returns session details, HPE fields load and validate, submit resolves successfully, and CompleteSession succeeds exactly once.

Prerequisites

Complete these setup items before coding integration steps. This prevents most first-run failures.

  1. Generate RSA keys first and secure private key material. Follow RSA key generation (clean setup).
  2. Prepare Postman environment with privateEncryptedKeyPEM and password to validate signatures before app integration.
  3. Confirm NCR onboarding values: X-CompanyNumber, X-StoreNumber, and allowed origins for ReturnURL/FrameHostingURL.
  4. Set server environment variables and keep private key usage server-side only.
Recommended onboarding order

RSA setup → Postman end-to-end validation → iframe integration in your app → reversal and mobile extensions.

RSA key generation (clean setup)

Use the Dev Portal RSA Key Generation Studio output as your single source for Postman and backend signing inputs.

Download RSA Key Generator (.zip)

Step-by-step

  1. Open RsaKeyGenerationStudio.exe from the Dev Portal package.
  2. Enter key prefix (for example: KC250630-STORE190).
  3. Use key size 2048 or 4096 (recommended default: 2048).
  4. Set a key secret so private PEM is exported as encrypted PKCS#8.
  5. Generate and verify these files: {Prefix}.Private.pem, {Prefix}.Public.pem, {Prefix}.Private.xml, {Prefix}.Public.xml.

Where each key file is used

FileUsed byPurpose
{Prefix}.Private.pemPostman (privateEncryptedKeyPEM)RSA signing for InitializeSession / CompleteSession scripts
{Prefix}.Public.pemPostman optional verificationSignature verification during troubleshooting
{Prefix}.Public.xmlMobile registration bodyClientPublicKey for RegisterMobileDevice
{Prefix}.Private.xmlLegacy tools onlyNot required in standard HPP browser iframe flow
Security baseline

Never commit private keys, PEM passwords, or full Postman environments with secrets to source control. Keep key material in secure secret stores and rotate periodically.

Postman end-to-end sandbox

Let merchants validate the complete purchase flow without writing app code. Use this Postman-first path aligned with the Dev Portal collections to verify configuration, signing, and API responses before frontend integration.

Download Postman Collection Package (.zip)

Recommended collection flow

  1. Import WebEPS API New 2026 for Ziosk collection and matching environment.
  2. Set environment values: baseURL, API Version, Company Number, Store Number, Client Application Name.
  3. Set key material: privateEncryptedKeyPEM and password.
  4. Run InitializeSession request (pre-request script injects RequestExpirationUTC and X-Signature).
  5. Open returned RequestURL in browser and complete payment.
  6. Run CompleteSession using saved CompleteSession collection variable.
Swagger-style interactive option

Need a guided API explorer with grouped endpoints and multi-language snippets? Click below to open and explore.

Open API Reference (Swagger)

Architecture

The integration has three actors: your Application Server, your Browser / Client, and the NCR HPE API. The browser never calls NCR directly — all signed API calls go through your server. Payment fields are rendered as secure iframes hosted by NCR; card data never touches your application.

⚠️ Critical — never call NCR APIs from the browser

InitializeSession and CompleteSession must be called from your Application Server. Your NCR private key and company credentials must never be exposed client-side.

StepWhere it runsDescription
InitializeSessionServerSigns and POSTs to NCR; returns session details to browser
Load HPE SDKBrowserCalls ncr.elements() to render hosted payment fields
Payment field iframesBrowserEach field renders in its own NCR-hosted iframe
Submit formBrowserCalls paymentForm.submit(submissionConfig) with session details
CompleteSessionServerFinalises the transaction — call only after submit resolves successfully

Integration flow

Browser / Client Your frontend Your App Server Node / Express backend NCR HPE API NCR Voyix Host 1 INITIALIZE SESSION POST /api/payments/initialize InitializeSession (RSA-signed) SessionId + RequestURL { sessionId, requestURL } 2 LOAD IFRAME iframe src = RequestURL Load HPP payment form (iframe) postMessage: PAGE_LOAD_COMPLETE postMessage: IFRAME_RESIZE { height } 3 CUSTOMER PAYS Customer enters card inside iframe Process card / wallet token Authorisation result Redirect iframe → ReturnURL?statusCode=100 4 PAYMENT_RETURN ReturnURL page loaded postMessage: PAYMENT_RETURN { statusCode } statusCode === 100 → proceed to CompleteSession 5 COMPLETE SESSION POST /api/payments/complete { sessionId } CompleteSession (RSA-signed) Transaction finalised { success: true } ✓ Navigate to order confirmation Browser / Client Your App Server NCR HPE API

Step 1 — InitializeSession

Endpoint: POST /v1.4/PaymentTransactionManagement/InitializeSession  Server-side

Call this from your backend when the customer proceeds to checkout. Sign the request body with your RSA-SHA256 private key and pass back only sessionId and the session details to the browser for use in paymentForm.submit().

Required headers

HeaderRequiredDescription
Content-TypeYesapplication/json or text/xml
X-CompanyNumberYesMerchant company number
X-StoreNumberYesMerchant store number
X-ReferenceIdYesUnique correlation ID per request
X-Client-Application-NameYesClient integration identifier
X-Client-Application-VersionOptionalClient application version
X-SignatureYesRSA-SHA256 signature generated server-side
curl -X POST "{{baseURL}}/v1.4/PaymentTransactionManagement/InitializeSession" \
  -H "Content-Type: application/json" \
  -H "X-CompanyNumber: {{Company Number}}" \
  -H "X-StoreNumber: {{Store Number}}" \
  -H "X-ReferenceId: {{$guid}}" \
  -H "X-Client-Application-Name: {{Client Application Name}}" \
  -H "X-Client-Application-Version: {{Client Application Name Version}}" \
  -H "X-Signature: {{GeneratedSignature}}" \
  -d '{
    "Amount": 59.99,
    "ReturnMethod": "Redirect",
    "ReturnURL": "https://merchant.example.com/payment-return",
    "FrameHostingURL": "https://merchant.example.com",
    "RequestExpirationUTC": "2026-06-30T12:35:00.000Z",
    "TokenRetrievalRequired": true,
    "AccountAddressCollectionMode": "Full",
    "ValidateAccountSecurityCode": true,
    "ValidateAccountAddress": true
  }'
http POST {{baseURL}}/v1.4/PaymentTransactionManagement/InitializeSession \
  Content-Type:application/json \
  X-CompanyNumber:{{Company Number}} \
  X-StoreNumber:{{Store Number}} \
  X-ReferenceId:{{$guid}} \
  X-Client-Application-Name:{{Client Application Name}} \
  X-Client-Application-Version:{{Client Application Name Version}} \
  X-Signature:{{GeneratedSignature}} \
  Amount:=59.99 \
  ReturnMethod=Redirect \
  ReturnURL=https://merchant.example.com/payment-return \
  FrameHostingURL=https://merchant.example.com \
  RequestExpirationUTC=2026-06-30T12:35:00.000Z \
  TokenRetrievalRequired:=true \
  AccountAddressCollectionMode=Full \
  ValidateAccountSecurityCode:=true \
  ValidateAccountAddress:=true
import requests

url = "{{baseURL}}/v1.4/PaymentTransactionManagement/InitializeSession"
headers = {
  "Content-Type": "application/json",
  "X-CompanyNumber": "{{Company Number}}",
  "X-StoreNumber": "{{Store Number}}",
  "X-ReferenceId": "{{$guid}}",
  "X-Client-Application-Name": "{{Client Application Name}}",
  "X-Client-Application-Version": "{{Client Application Name Version}}",
  "X-Signature": "{{GeneratedSignature}}"
}
payload = {
  "Amount": 59.99,
  "ReturnMethod": "Redirect",
  "ReturnURL": "https://merchant.example.com/payment-return",
  "FrameHostingURL": "https://merchant.example.com",
  "RequestExpirationUTC": "2026-06-30T12:35:00.000Z",
  "TokenRetrievalRequired": True,
  "AccountAddressCollectionMode": "Full",
  "ValidateAccountSecurityCode": True,
  "ValidateAccountAddress": True
}

response = requests.post(url, headers=headers, json=payload, timeout=30)
print(response.status_code)
print(response.text)
using var client = new HttpClient();
using var req = new HttpRequestMessage(HttpMethod.Post,
    "{{baseURL}}/v1.4/PaymentTransactionManagement/InitializeSession");

req.Headers.TryAddWithoutValidation("X-CompanyNumber", "{{Company Number}}");
req.Headers.TryAddWithoutValidation("X-StoreNumber", "{{Store Number}}");
req.Headers.TryAddWithoutValidation("X-ReferenceId", "{{$guid}}");
req.Headers.TryAddWithoutValidation("X-Client-Application-Name", "{{Client Application Name}}");
req.Headers.TryAddWithoutValidation("X-Client-Application-Version", "{{Client Application Name Version}}");
req.Headers.TryAddWithoutValidation("X-Signature", "{{GeneratedSignature}}");

req.Content = new StringContent("{\"Amount\":59.99,\"ReturnMethod\":\"Redirect\",\"ReturnURL\":\"https://merchant.example.com/payment-return\",\"FrameHostingURL\":\"https://merchant.example.com\",\"RequestExpirationUTC\":\"2026-06-30T12:35:00.000Z\",\"TokenRetrievalRequired\":true,\"AccountAddressCollectionMode\":\"Full\",\"ValidateAccountSecurityCode\":true,\"ValidateAccountAddress\":true}", Encoding.UTF8, "application/json");

var res = await client.SendAsync(req);
Console.WriteLine((int)res.StatusCode);
Console.WriteLine(await res.Content.ReadAsStringAsync());
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
  .uri(URI.create("{{baseURL}}/v1.4/PaymentTransactionManagement/InitializeSession"))
  .header("Content-Type", "application/json")
  .header("X-CompanyNumber", "{{Company Number}}")
  .header("X-StoreNumber", "{{Store Number}}")
  .header("X-ReferenceId", "{{$guid}}")
  .header("X-Client-Application-Name", "{{Client Application Name}}")
  .header("X-Client-Application-Version", "{{Client Application Name Version}}")
  .header("X-Signature", "{{GeneratedSignature}}")
  .POST(HttpRequest.BodyPublishers.ofString("{\"Amount\":59.99,\"ReturnMethod\":\"Redirect\",\"ReturnURL\":\"https://merchant.example.com/payment-return\",\"FrameHostingURL\":\"https://merchant.example.com\",\"RequestExpirationUTC\":\"2026-06-30T12:35:00.000Z\",\"TokenRetrievalRequired\":true,\"AccountAddressCollectionMode\":\"Full\",\"ValidateAccountSecurityCode\":true,\"ValidateAccountAddress\":true}"))
  .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
{
  "Amount": 59.99,
  "ReturnMethod": "Redirect",
  "ReturnURL": "https://merchant.example.com/payment-return",
  "FrameHostingURL": "https://merchant.example.com",
  "RequestExpirationUTC": "2026-06-30T12:35:00.000Z",
  "TokenRetrievalRequired": true,
  "AccountAddressCollectionMode": "Full",
  "ValidateAccountSecurityCode": true,
  "ValidateAccountAddress": true
}
<InitializeSessionRequest>
  <Amount>59.99</Amount>
  <ReturnMethod>Redirect</ReturnMethod>
  <ReturnURL>https://merchant.example.com/payment-return</ReturnURL>
  <FrameHostingURL>https://merchant.example.com</FrameHostingURL>
  <RequestExpirationUTC>2026-06-30T12:35:00.000Z</RequestExpirationUTC>
  <TokenRetrievalRequired>true</TokenRetrievalRequired>
  <AccountAddressCollectionMode>Full</AccountAddressCollectionMode>
  <ValidateAccountSecurityCode>true</ValidateAccountSecurityCode>
  <ValidateAccountAddress>true</ValidateAccountAddress>
</InitializeSessionRequest>
{
  "200": {
    "SessionId": "45016f4cce57e5ab4e308a1822b9b9fc3336",
    "RequestURL": "https://.../WebEPS/HPP?session=45016f4cce57e5ab4e308a1822b9b9fc3336"
  },
  "400": {
    "ErrorCode": "INVALID_REQUEST",
    "Message": "Request body or headers are invalid.",
    "Details": "Verify required fields and signature payload."
  },
  "401": {
    "ErrorCode": "UNAUTHORIZED",
    "Message": "Authentication failed.",
    "Details": "Invalid company/store credentials, signature, or allowed origin setup."
  }
}
<Responses>
  <Status code="200">
    <InitializeSessionResponse>
      <SessionId>45016f4cce57e5ab4e308a1822b9b9fc3336</SessionId>
      <RequestURL>https://.../WebEPS/HPP?session=45016f4cce57e5ab4e308a1822b9b9fc3336</RequestURL>
    </InitializeSessionResponse>
  </Status>
  <Status code="400">
    <Error>
      <Code>INVALID_REQUEST</Code>
      <Message>Request body or headers are invalid.</Message>
      <Details>Verify required fields and signature payload.</Details>
    </Error>
  </Status>
  <Status code="401">
    <Error>
      <Code>UNAUTHORIZED</Code>
      <Message>Authentication failed.</Message>
      <Details>Invalid company/store credentials, signature, or allowed origin setup.</Details>
    </Error>
  </Status>
</Responses>

Use uniqueTransactionIdentifier and requestUrl from the response in your paymentForm.submit() call. Save SessionId — you'll need it for CompleteSession.

Step 2 — Elements SDK Setup

Load the HPE SDK script in your page, then call ncr.elements() to instantiate the hosted payment form. Each payment field renders in its own NCR-hosted iframe — card data never passes through your application.

Step 2a — Load the SDK

Include the Elements SDK script in your HTML <head> or before the closing </body> tag. Replace <environment-domain> and {version} with the values provided by NCR.

HTML
<script src="https://<environment-domain>/securepay/elements/{version}/sdk.js"></script>

Step 2b — Configure payment fields

Call ncr.elements() with a configuration object. The sample below shows a complete working integration; the table that follows describes every configuration field in detail.

Complete integration walkthrough

This sample covers loading the SDK, configuring each payment field, applying custom styles, handling events, and submitting the form to process a payment.

HTML containers

Add a placeholder <div> for each payment field. The SDK injects an iframe into each container at runtime.

HTML
<!-- Payment field containers -->
<div>
  <label for="field-nameOnCard">Name on Card</label>
  <div id="field-nameOnCard" class="field"></div>
</div>
<div>
  <label for="field-cardNumber">Card Number</label>
  <div id="field-cardNumber" class="field"></div>
</div>
<div>
  <label for="field-expiration">Expiration</label>
  <div id="field-expiration" class="field"></div>
</div>
<div>
  <label for="field-securityCode">Security Code</label>
  <div id="field-securityCode" class="field"></div>
</div>
<div>
  <label for="field-postalCode">Zip Code</label>
  <div id="field-postalCode" class="field"></div>
</div>
<button id="submit-btn" disabled>Pay</button>

Complete JavaScript sample

Initialize the payment form with ncr.elements(), configure fields and styles, set up event hooks, and wire up the submit button.

JavaScript
const btn = document.getElementById("submit-btn");
let paymentFormObj = "";

const promisingObject = ncr.elements({
  data: {
    paymentType: "CHECKOUT", // Options: CHECKOUT or WALLET
    method: "CARD",
    domain: ["https://your-app.com"], // Whitelisted domains
    brands: ["visa", "master", "amex", "discover", "MAESTRO", "JCB", "UNIONPAY", "DINERS-CLUB"],
    fields: {
      nameOnCard: {
        parentElement: "field-nameOnCard",
        placeholder: "Enter name on card",
        value: "John"
      },
      cardNumber: {
        parentElement: "field-cardNumber",
        enableFormatting: true,
        masking: {
          character: "*",
          mode: "ALWAYS_MASK_ALL", // NO_MASKING | ALWAYS_MASK_EXCEPT_LAST_4 | ALWAYS_MASK_ALL
                                   // BLUR_MASK_EXCEPT_LAST_4 | BLUR_MASK_All
                                   // BLUR_MASK_EXCEPT_LAST_4_SHRINK | BLUR_MASK_ALL_SHRINK
          shrinkLength: "8"        // Only applies to *_SHRINK modes
        }
      },
      expiration: {
        parentElement: "field-expiration",
        placeholder: "MM / YY",
        format: "MM_YY"            // MM_YY or MM_YYYY
      },
      securityCode: {
        parentElement: "field-securityCode",
        masking: {
          // NO_MASKING | ALWAYS_MASK_ALL | BLUR_MASK_All
          mode: "ALWAYS_MASK_ALL"
        },
        brand: "<brand-type>"     // Drives CVV length: visa -> 3, amex -> 4 (default: 4)
      },
      zipCode: {
        parentElement: "field-postalCode"
      },
    },
    cssClassNames: {
      invalid: "invalid",
      valid: "valid"
    },
    css: {
      input: {
        "font-family": "BeautifulPeople",
        "font-size": "16px",
        color: "#00a9e0",
        "outline-style": "dotted"
      },
      ".valid": { color: "#43B02A" },
      ".invalid": { color: "#C01324" },
      "input::placeholder": { color: "#aaa" }
    },
    font: {
      data: "<base64_encoded_font_data>",
      family: "BeautifulPeople",
      // Supported: font/otf | font/ttf | font/woff | font/woff2
      format: "font/ttf",
      integrity: "l3g1AMGSOJ4lOAHqnEIEtk9qip5LK2OuLFJl35A7F9I="
    },
  },
  events: {
    onCardBrandChange: (cardBrand, formState, form) => {
      document.getElementById("status-cardBrand").innerText = cardBrand ?? "";
    },
    onFormValid: (formState, form) => {
      document.getElementById("status-formValid").innerText = "TRUE";
      btn.removeAttribute("disabled");
    },
    onFormInValid: (formState, form) => {
      document.getElementById("status-formValid").innerText = "FALSE";
      btn.setAttribute("disabled", "true");
    },
    onFocus: (field, fieldState) => {
      switch (field) {
        case "expiration":
          document.getElementById("field-expiration").classList.add("focus");
          break;
        case "securityCode":
          document.getElementById("field-securityCode").classList.add("focus");
          break;
      }
    },
    onLostFocus: () => {},
    onFieldValidityChange: () => {},
  },
});

promisingObject.then((paymentForm) => {
  paymentFormObj = paymentForm;
  btn.addEventListener("click", (e) => {
    document.getElementById("api-response").innerText = "Loading...";
    const submissionConfig = {
      sessionDetails: {
        uniqueTransactionIdentifier: "<from InitializeSession>",
        requestUrl: "https://<environment-domain>/ecommerce/SecurePay/Checkout/?companyNumber=<COMPANY_NUMBER>"
      },
      tokens: [
        {
          tokenType: "201",
          tokenValue: "<saved_token_value>"
        }
      ],
      params: {
        billingDetails: {
          fullName: "<cardholder-name>",
          address1: "3456 Dublin Rd",
          address2: "APT 430",
          city: "Cupertino",
          state: "CA",
          country: "USA",
          postalCode: "95234",
          isBillingSameAsShipping: true,
        },
        profileDetails: {
          isDefaultCard: true,
          nickName: "Mike",
        },
      },
    };
    paymentForm.submit(submissionConfig).then((response) => {
      document.getElementById("api-response").innerText = JSON.stringify(response);
    });
  });
});
FieldR / ODescription
paymentTypeRequiredTransaction type. Supported value: "CHECKOUT"
methodRequiredPayment method. Supported value: "CARD"
brandsOptionalList of supported card brands. If empty, all major brands are accepted.
fieldsRequiredField-level configuration including placeholders, formatting, and masking.
parentElementRequiredID of the container element where each hosted field iframe is embedded.
placeholderOptionalPlaceholder text displayed inside the input field.
enableFormattingOptionalIf true, automatically formats field input (e.g. card number groups).
maskingOptionalDefines how field values are masked as the user types.
characterOptionalThe masking character (e.g. "*").
modeOptionalMasking mode: NO_MASKING, ALWAYS_MASK_ALL, ALWAYS_MASK_EXCEPT_LAST_4, BLUR_MASK_ALL, BLUR_MASK_EXCEPT_LAST_4, BLUR_MASK_ALL_SHRINK, BLUR_MASK_EXCEPT_LAST_4_SHRINK.
shrinkLengthOptionalNumber of digits to shrink when using a *_SHRINK masking mode.
cssClassNamesOptionalCSS class names applied for valid / invalid validation states.
cssOptionalCustom CSS styling rules injected into each hosted field iframe.
fontOptionalCustom font settings: data (base64), family, format (font/otf, font/ttf, font/woff), and integrity.
promiseOptionalThe promise resolves to an instance of the payment form on success, or an error on failure.
Supported card brands

Visa, Mastercard, American Express, Discover, Maestro, JCB, UnionPay, Diners Club

Step 2c — Submit the form

After ncr.elements() resolves, call paymentForm.submit(submissionConfig) with the session details from InitializeSession. The promise resolves with the payment response.

JavaScript
promisingObject.then((paymentForm) => {
  paymentFormObj = paymentForm;
  btn.addEventListener("click", (e) => {
    const submissionConfig = {
      sessionDetails: {
        uniqueTransactionIdentifier: "********",
        requestUrl: "https://<environment-domain>/ecommerce/SecurePay/Checkout/?companyNumber=<COMPANY_NUMBER>"
      },
      tokens: [
        {
          tokenType: "201",
          tokenValue: "<saved_token_value>"
        }
      ],
      params: {
        billingDetails: {
          fullName: "<cardholder-name>",
          address1: "3456 Dublin Rd",
          address2: "APT 430",
          city: "Cupertino",
          state: "CA",
          country: "USA",
          postalCode: "95234",
          isBillingSameAsShipping: true,
        },
        profileDetails: {
          isDefaultCard: true,
          nickName: 'Mike',
        },
      },
    };
    const publicKey = "merchant-public-key";
    paymentForm.submit(submissionConfig).then((response) => {
      // Response status
    });
  });
});
FieldR / ODescription
uniqueTransactionIdentifierRequiredUnique ID for the transaction session, returned by InitializeSession.
requestUrlRequiredPayment endpoint URL returned by InitializeSession.
companyNumberRequiredMerchant company number, appended to the requestUrl.
fullNameOptionalCardholder full name for billing.
address1OptionalPrimary billing address line.
address2OptionalSecondary address line (e.g., apartment number).
cityOptionalCity of the billing address.
stateOptionalState or province of the billing address.
countryOptionalCountry of the billing address.
postalCodeOptionalPostal / ZIP code used for AVS validation.
isBillingSameAsShippingOptionalSet to true if the billing address matches the shipping address.
isDefaultCardOptionalFlag the card as the default saved card for this profile.
nickNameOptionalA friendly nickname to identify the saved card.
tokensOptionalArray of saved token objects to use for this transaction.
tokenTypeConditionalRequired when tokens are provided. Token type (e.g. "201").
tokenValueConditionalRequired when tokens are provided. The saved token value.

Step 3 — CompleteSession

Endpoint: POST /v1.4/PaymentTransactionManagement/CompleteSession  Server-side

Call this from your server only after paymentForm.submit() resolves with a success status. Like InitializeSession, it must be RSA-SHA256 signed server-side.

Guard against double-calls (React StrictMode)

Use a useRef flag (completingRef.current) to ensure CompleteSession is invoked exactly once per session, even in React StrictMode.

Required headers

HeaderRequiredDescription
Content-TypeYesapplication/json or text/xml
X-CompanyNumberYesMerchant company number
X-StoreNumberYesMerchant store number
X-ReferenceIdYesUnique correlation ID per request
X-SignatureYesRSA-SHA256 signature generated server-side
curl -X POST "{{baseURL}}/v1.4/PaymentTransactionManagement/CompleteSession" \
  -H "Content-Type: application/json" \
  -H "X-CompanyNumber: {{Company Number}}" \
  -H "X-StoreNumber: {{Store Number}}" \
  -H "X-ReferenceId: {{$guid}}" \
  -H "X-Signature: {{GeneratedSignature}}" \
  -d '{
    "SessionId": "{{CompleteSession}}",
    "RequestExpirationUTC": "2026-06-30T12:35:00.000Z"
  }'
http POST {{baseURL}}/v1.4/PaymentTransactionManagement/CompleteSession \
  Content-Type:application/json \
  X-CompanyNumber:{{Company Number}} \
  X-StoreNumber:{{Store Number}} \
  X-ReferenceId:{{$guid}} \
  X-Signature:{{GeneratedSignature}} \
  SessionId={{CompleteSession}} \
  RequestExpirationUTC=2026-06-30T12:35:00.000Z
import requests

url = "{{baseURL}}/v1.4/PaymentTransactionManagement/CompleteSession"
headers = {
  "Content-Type": "application/json",
  "X-CompanyNumber": "{{Company Number}}",
  "X-StoreNumber": "{{Store Number}}",
  "X-ReferenceId": "{{$guid}}",
  "X-Signature": "{{GeneratedSignature}}"
}
payload = {
  "SessionId": "{{CompleteSession}}",
  "RequestExpirationUTC": "2026-06-30T12:35:00.000Z"
}

response = requests.post(url, headers=headers, json=payload, timeout=30)
print(response.status_code)
print(response.text)
using var client = new HttpClient();
using var req = new HttpRequestMessage(HttpMethod.Post,
    "{{baseURL}}/v1.4/PaymentTransactionManagement/CompleteSession");

req.Headers.TryAddWithoutValidation("X-CompanyNumber", "{{Company Number}}");
req.Headers.TryAddWithoutValidation("X-StoreNumber", "{{Store Number}}");
req.Headers.TryAddWithoutValidation("X-ReferenceId", "{{$guid}}");
req.Headers.TryAddWithoutValidation("X-Signature", "{{GeneratedSignature}}");

req.Content = new StringContent("{\"SessionId\":\"{{CompleteSession}}\",\"RequestExpirationUTC\":\"2026-06-30T12:35:00.000Z\"}", Encoding.UTF8, "application/json");

var res = await client.SendAsync(req);
Console.WriteLine((int)res.StatusCode);
Console.WriteLine(await res.Content.ReadAsStringAsync());
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
  .uri(URI.create("{{baseURL}}/v1.4/PaymentTransactionManagement/CompleteSession"))
  .header("Content-Type", "application/json")
  .header("X-CompanyNumber", "{{Company Number}}")
  .header("X-StoreNumber", "{{Store Number}}")
  .header("X-ReferenceId", "{{$guid}}")
  .header("X-Signature", "{{GeneratedSignature}}")
  .POST(HttpRequest.BodyPublishers.ofString("{\"SessionId\":\"{{CompleteSession}}\",\"RequestExpirationUTC\":\"2026-06-30T12:35:00.000Z\"}"))
  .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
{
  "SessionId": "{{CompleteSession}}",
  "RequestExpirationUTC": "2026-06-30T12:35:00.000Z"
}
<CompleteSessionRequest>
  <SessionId>{{CompleteSession}}</SessionId>
  <RequestExpirationUTC>2026-06-30T12:35:00.000Z</RequestExpirationUTC>
</CompleteSessionRequest>
{
  "200": {
    "TransactionStatus": "Approved",
    "AuthorizationCode": "A12345",
    "ReferenceNumber": "{{ReferenceNumber}}"
  },
  "400": {
    "ErrorCode": "INVALID_REQUEST",
    "Message": "SessionId is missing or invalid.",
    "Details": "Verify request payload and expiration timestamp."
  },
  "401": {
    "ErrorCode": "UNAUTHORIZED",
    "Message": "Authentication failed.",
    "Details": "Invalid signature or merchant credentials."
  }
}
<Responses>
  <Status code="200">
    <CompleteSessionResponse>
      <TransactionStatus>Approved</TransactionStatus>
      <AuthorizationCode>A12345</AuthorizationCode>
      <ReferenceNumber>{{ReferenceNumber}}</ReferenceNumber>
    </CompleteSessionResponse>
  </Status>
  <Status code="400">
    <Error>
      <Code>INVALID_REQUEST</Code>
      <Message>SessionId is missing or invalid.</Message>
      <Details>Verify request payload and expiration timestamp.</Details>
    </Error>
  </Status>
  <Status code="401">
    <Error>
      <Code>UNAUTHORIZED</Code>
      <Message>Authentication failed.</Message>
      <Details>Invalid signature or merchant credentials.</Details>
    </Error>
  </Status>
</Responses>

Digital Wallets — Google Pay™ & Apple Pay

The NCR Hosted Payment Elements component supports digital wallet payments. Configure the wallet fields in your Elements component to render the Apple Pay and Google Pay buttons. To complete wallet payments end to end, your account must also be onboarded and enabled by NCR.

🇬
Google Pay
Works on Chrome and Android. Customers pay with cards saved in their Google account. NCR processes the encrypted payment token server-side.
🍎
Apple Pay
Available on Safari (iOS & macOS). Customers authenticate with Face ID or Touch ID. Requires your domain to be Apple Pay verified.
Same flow as standard cards

Wallet buttons are rendered by the Elements component when the wallet fields are configured. NCR onboarding and backend enablement are required for the wallet payment flow to work end to end. The PAYMENT_RETURN and CompleteSession flow is identical to standard card payments.

Configure wallet fields

Add containers for the wallet buttons, then include the wallet fields in the same ncr.elements() configuration as your card fields. The fields render the buttons; NCR onboarding is still required before a wallet payment can be authorised.

html + javascript
<div id="apple-pay-container"></div>
<div id="google-pay-container"></div>

<script>
const paymentForm = ncr.elements({
  data: {
    fields: {
      applePay: {
        parentElement: 'apple-pay-container',
        merchantInfo: {
          totalAmount: '9.99'
        },
        merchantDetails: {
          company: 949,
          store: 1,
          lane: 0 // lane is optional and defaults to 0
        },
        buttonType: 'buy',
        buttonStyle: 'black',
        height: 40,
        borderRadius: 6,
        sessionDetails: {
          uniqueTransactionIdentifier: '<unique-transaction-id>',
          responseUrl: '<checkout-response-url>'
        }
      },
      googlePay: {
        parentElement: 'google-pay-container',
        merchantInfo: {
          totalAmount: '9.99'
        },
        merchantDetails: {
          company: 949,
          store: 1,
          lane: 0 // lane is optional and defaults to 0
        },
        buttonType: 'buy',
        buttonColor: 'default',
        buttonSizeMode: 'fill',
        height: 40,
        borderRadius: 6,
        sessionDetails: {
          uniqueTransactionIdentifier: '<unique-transaction-id>',
          responseUrl: '<checkout-response-url>'
        }
      }
    }
  }
});
</script>

Google Pay™

Configure Google Pay in the Elements component to render the Google Pay button on supported browsers. NCR handles the Google Pay session, token decryption, and payment authorisation after your account has completed onboarding.

Onboarding required before go-live

Google Pay uses the Google Pay Mass Enablement model. Contact your NCR account team to provide the required onboarding information and enable Google Pay for your account.

Apple Pay

Configure Apple Pay in the Elements component to render the Apple Pay button when eligibility conditions are met. The hosted payment experience uses the Apple Pay Mass Enablement model — NCR manages the Apple Pay certificate infrastructure and registration with Apple on your behalf.

Merchant steps before go-live

Before Apple Pay can be activated you must:

  1. Host the domain verification file — serve the NCR-provided Apple Pay domain association file at https://your-domain.com/.well-known/apple-developer-merchantid-domain-association with no redirect and Content-Type: text/plain.
  2. Provide your domain and URL to NCR — contact your NCR account team with your merchant domain and hosted payment URL. NCR will trigger domain registration with Apple and configure the certificate infrastructure.

Once NCR confirms activation, the configured Apple Pay field can complete payments when the shopper's browser meets Apple's eligibility requirements.

Enable all payment methods together

You can enable all payment methods simultaneously. The hosted payment experience displays the appropriate wallet buttons based on what the customer's browser supports.

API Reference — Request headers

HeaderExample valueDescription
Content-Typeapplication/jsonAlways required
X-CompanyNumberYOUR_COMPANY_NUMBERYour NCR company number
X-StoreNumberYOUR_STORE_NUMBERYour NCR store number
X-ReferenceId32-char hexUnique per request — crypto.randomUUID().replace(/-/g,"")
X-Client-Application-NameYourAppNameIdentifier for your integration
X-Client-Application-Version1.0.0Your application version string
X-SignatureUppercase hexRSA-SHA256 signature of the full serialised request body — server-side only

API Reference — Request body fields

FieldTypeRequiredDescription
AmountnumberRequiredTotal charge in dollars (e.g. 59.99)
ReturnURLstringRequiredYour page that receives statusCode after redirect
FrameHostingURLstringRequiredOrigin of the page hosting the iframe (for postMessage trust)
ReturnMethodstringRequired"Redirect" — iframe navigates to ReturnURL on completion
RequestExpirationUTCstring (ISO)RequiredSession expiry — typically 5 min from now
PaymentMethodTypestring[]Optional["CreditCard"] — controls which payment buttons appear
AuthorizationModestringOptional"Sale" (purchase flow)
TokenRetrievalRequiredbooleanOptionalReturn a payment token for future use
AccountAddressCollectionModestringOptional"Full" | "PostalCodeOnly" | "None"
ValidateAccountSecurityCodebooleanOptionalRequire CVV entry (set false for wallet payments)
ValidateAccountAddressbooleanOptionalRequire billing address in the iframe
CardTypeobjectOptional{ Type: "Credit"|"Debit", Name: "Visa"|"MasterCard"… }
TimeoutInMinutesnumber | nullOptionalSession timeout override (default 5 min)
AllowCardNamingbooleanOptionalAllow customer to name/save their card

Status code reference

CodeLabelDescriptionRecommended action
100✅ SuccessPayment completed successfullyCall CompleteSession → navigate to confirmation
201⚠️ CancelledCustomer cancelled the paymentShow retry option
202❌ Network ErrorConnectivity issue during transactionAsk user to retry
203❌ Server RejectTransaction rejected by NCR serverLog & contact support
204❌ Technical ErrorInternal technical issueRetry or contact support
205⚠️ Session ExpiredPayment session has expired (> 5 min)Re-initialise session
206❌ External ErrorError in an external systemRetry later
207❌ Invalid RequestMalformed or invalid request bodyCheck request fields
208❌ Auth FailedAuthentication / 3DS failedAsk for a different card
209⚠️ TimeoutPayment request timed outRetry

Reversal flow (TOR / Void)

The Dev Portal happy-path package also includes a reversal path for timeout and void scenarios. Use this when payment was authorized but your client flow did not complete cleanly.

Endpoint: POST /v1.4/ReversalAdministration/ReverseTransaction  Server-side

Call reversal from your server only

Use the same signed-header model as InitializeSession and CompleteSession. Keep private keys and merchant credentials out of browser code.

Required headers

HeaderRequiredDescription
Content-TypeYesapplication/json or text/xml
X-CompanyNumberYesMerchant company number
X-StoreNumberYesMerchant store number
X-ReferenceIdYesUnique correlation ID per request
X-SignatureYesRSA-SHA256 signature generated server-side
curl -X POST "{{baseURL}}/v1.4/ReversalAdministration/ReverseTransaction" \
  -H "Content-Type: application/json" \
  -H "X-CompanyNumber: {{Company Number}}" \
  -H "X-StoreNumber: {{Store Number}}" \
  -H "X-ReferenceId: {{$guid}}" \
  -H "X-Signature: {{GeneratedSignature}}" \
  -d '{
    "OriginalReferenceId": "{{OriginalReferenceId}}",
    "ReversalType": "timeout",
    "RequestExpirationUTC": "2026-06-30T12:35:00.000Z"
  }'
http POST {{baseURL}}/v1.4/ReversalAdministration/ReverseTransaction \
  Content-Type:application/json \
  X-CompanyNumber:{{Company Number}} \
  X-StoreNumber:{{Store Number}} \
  X-ReferenceId:{{$guid}} \
  X-Signature:{{GeneratedSignature}} \
  OriginalReferenceId={{OriginalReferenceId}} \
  ReversalType=timeout \
  RequestExpirationUTC=2026-06-30T12:35:00.000Z
import requests

url = "{{baseURL}}/v1.4/ReversalAdministration/ReverseTransaction"
headers = {
  "Content-Type": "application/json",
  "X-CompanyNumber": "{{Company Number}}",
  "X-StoreNumber": "{{Store Number}}",
  "X-ReferenceId": "{{$guid}}",
  "X-Signature": "{{GeneratedSignature}}"
}
payload = {
  "OriginalReferenceId": "{{OriginalReferenceId}}",
  "ReversalType": "timeout",
  "RequestExpirationUTC": "2026-06-30T12:35:00.000Z"
}

response = requests.post(url, headers=headers, json=payload, timeout=30)
print(response.status_code)
print(response.text)
using var client = new HttpClient();
using var req = new HttpRequestMessage(HttpMethod.Post,
    "{{baseURL}}/v1.4/ReversalAdministration/ReverseTransaction");

req.Headers.TryAddWithoutValidation("X-CompanyNumber", "{{Company Number}}");
req.Headers.TryAddWithoutValidation("X-StoreNumber", "{{Store Number}}");
req.Headers.TryAddWithoutValidation("X-ReferenceId", "{{$guid}}");
req.Headers.TryAddWithoutValidation("X-Signature", "{{GeneratedSignature}}");

req.Content = new StringContent("{\"OriginalReferenceId\":\"{{OriginalReferenceId}}\",\"ReversalType\":\"timeout\",\"RequestExpirationUTC\":\"2026-06-30T12:35:00.000Z\"}", Encoding.UTF8, "application/json");

var res = await client.SendAsync(req);
Console.WriteLine((int)res.StatusCode);
Console.WriteLine(await res.Content.ReadAsStringAsync());
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
  .uri(URI.create("{{baseURL}}/v1.4/ReversalAdministration/ReverseTransaction"))
  .header("Content-Type", "application/json")
  .header("X-CompanyNumber", "{{Company Number}}")
  .header("X-StoreNumber", "{{Store Number}}")
  .header("X-ReferenceId", "{{$guid}}")
  .header("X-Signature", "{{GeneratedSignature}}")
  .POST(HttpRequest.BodyPublishers.ofString("{\"OriginalReferenceId\":\"{{OriginalReferenceId}}\",\"ReversalType\":\"timeout\",\"RequestExpirationUTC\":\"2026-06-30T12:35:00.000Z\"}"))
  .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
{
  "OriginalReferenceId": "{{OriginalReferenceId}}",
  "ReversalType": "timeout",
  "RequestExpirationUTC": "2026-06-30T12:35:00.000Z"
}
<ReverseTransactionRequest>
  <OriginalReferenceId>{{OriginalReferenceId}}</OriginalReferenceId>
  <ReversalType>timeout</ReversalType>
  <RequestExpirationUTC>2026-06-30T12:35:00.000Z</RequestExpirationUTC>
</ReverseTransactionRequest>
{
  "200": {
    "ReversalStatus": "Completed",
    "OriginalReferenceId": "{{OriginalReferenceId}}",
    "ReferenceNumber": "RV-1001"
  },
  "400": {
    "ErrorCode": "INVALID_REQUEST",
    "Message": "OriginalReferenceId is missing or invalid.",
    "Details": "Verify request payload and reversal type."
  },
  "401": {
    "ErrorCode": "UNAUTHORIZED",
    "Message": "Authentication failed.",
    "Details": "Invalid signature or merchant credentials."
  }
}
<Responses>
  <Status code="200">
    <ReverseTransactionResponse>
      <ReversalStatus>Completed</ReversalStatus>
      <OriginalReferenceId>{{OriginalReferenceId}}</OriginalReferenceId>
      <ReferenceNumber>RV-1001</ReferenceNumber>
    </ReverseTransactionResponse>
  </Status>
  <Status code="400">
    <Error>
      <Code>INVALID_REQUEST</Code>
      <Message>OriginalReferenceId is missing or invalid.</Message>
      <Details>Verify request payload and reversal type.</Details>
    </Error>
  </Status>
  <Status code="401">
    <Error>
      <Code>UNAUTHORIZED</Code>
      <Message>Authentication failed.</Message>
      <Details>Invalid signature or merchant credentials.</Details>
    </Error>
  </Status>
</Responses>

Environment variables

.env (server)
HPP_API_BASE_URL=https://{ncr-hpp-api-host}
NCR_COMPANY_NUMBER=YOUR_COMPANY_NUMBER
NCR_STORE_NUMBER=YOUR_STORE_NUMBER
NCR_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"
APP_BASE_URL=https://your-app.com