Accept online payments with Hosted Payment Page

Build a secure checkout quickly with NCR Voyix Hosted Payment Page (HPP). Embed a payment form, support cards plus Google Pay and Apple Pay, and keep card data outside your systems.

HPP is a server-driven payment flow: your backend creates a session, your frontend renders the hosted form in an <iframe>, and your backend finalizes the payment after customer completion.

How HPP works

  1. Create session on your server with InitializeSession and receive RequestURL.
  2. Show hosted checkout in your UI by loading RequestURL in an iframe and listening for payment return events.
  3. Complete payment on your server with CompleteSession once the return status indicates success.
Why teams choose HPP

Lower PCI scope, faster integration, and a consistent checkout 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. Use the API explorer reference to inspect headers, payloads, and language snippets: Open HPP API Explorer.
  4. Implement iframe flow: InitializeSession → iframe → postMessage → CompleteSession.
  5. Verify success criteria: receive statusCode = 100 and then call CompleteSession once.
First success criteria

Your setup is healthy when InitializeSession returns RequestURL, iframe loads, PAYMENT_RETURN gives 100, 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 RsaKeyGenerationStudio.zip (~81 MB, Windows win-x64 self-contained)

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.

Recommended collection flow

  1. Import NCR Postman WebEPS API 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 HPP API Explorer

Architecture

The integration has three actors: your Application Server, your Browser / Client, and the NCR HPP API. The browser never calls NCR directly — all signed API calls go through your server.

⚠️ 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 SessionId + RequestURL to browser
Load iframeBrowserSets <iframe src="{RequestURL}">
postMessage eventsBrowserListen for PAGE_LOAD_COMPLETE, IFRAME_RESIZE, PAYMENT_RETURN
ReturnURL pageBrowserPosts statusCode back to parent via postMessage
CompleteSessionServerFinalises the transaction — call only after statusCode === 100

Integration flow

Browser / Client Your frontend Your App Server Node / Express backend NCR HPP 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 / selects Google Pay or Apple Pay 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 HPP 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 requestURL to the browser.

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 RequestURL as the src of your payment iframe. Save SessionId — you'll need it for CompleteSession.

Step 2 — Embed the payment iframe

Set the iframe src to the RequestURL returned by InitializeSession. The iframe renders NCR's hosted payment form — card data never passes through your application.

const [iframeSrc, setIframeSrc] = useState<string | null>(null);
const [iframeHeight, setIframeHeight] = useState(500);

// After InitializeSession resolves:
setIframeSrc(result.requestURL);

// In JSX:
{iframeSrc && (
  <iframe
    src={iframeSrc}
    style={{ width: "100%", height: iframeHeight, border: "none" }}
    allow="payment"
    title="Secure Payment"
  />
)}
<iframe
  id="payment-frame"
  src="{RequestURL from InitializeSession}"
  style="width:100%; border:none; min-height:500px;"
  allow="payment"
  title="Secure Payment"
></iframe>

Step 3 — Handle postMessage events

The HPP iframe communicates back to your page via window.postMessage. Subscribe to three events.

⚠️ Always verify event.origin

Before acting on any postMessage, verify that event.origin includes "ncrvoyix.com" or equals your own origin. Unverified origins can be spoofed by malicious iframes.

window.addEventListener("message", (event) => {
  // Always verify origin before acting
  if (
    !event.origin.includes("ncrvoyix.com") &&
    event.origin !== window.location.origin
  ) return;

  const { messageType, height, statusCode } = event.data ?? {};

  switch (messageType) {

    case "PAGE_LOAD_COMPLETE":
      // iframe fully loaded — hide your loading spinner
      setIframeLoaded(true);
      break;

    case "IFRAME_RESIZE":
      // Dynamically resize iframe to match payment form content height
      if (height > 0) setIframeHeight(height);
      break;

    case "PAYMENT_RETURN":
      // Fired by your ReturnURL page after NCR redirects
      handleStatusCode(statusCode);
      break;
  }
});
// /payment-return — NCR redirects the iframe here with ?statusCode=NNN
const params = new URLSearchParams(window.location.search);
const statusCode = parseInt(params.get("statusCode") ?? "0", 10);

// Post the result back to the parent window (CheckoutPage)
window.parent.postMessage(
  { messageType: "PAYMENT_RETURN", statusCode },
  window.location.origin   // trusted origin only
);

postMessage events reference

EventPayloadAction
PAGE_LOAD_COMPLETE—Hide loading spinner; iframe is interactive
IFRAME_RESIZE{ height }Update iframe element height to heightpx
PAYMENT_RETURN{ statusCode }Handle payment result — see status code table

Step 4 — CompleteSession

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

Call this from your server only after statusCode === 100. 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>

Step 5 — Error handling

Handle all statusCode values returned via PAYMENT_RETURN. Only 100 is success — all others require specific user messaging.

TypeScript
async function handlePaymentReturn(statusCode: number) {

  if (statusCode === 100) {
    // ✅ SUCCESS — finalise on the server, then navigate to confirmation
    await completeSession(sessionId);
    navigateToOrderConfirm();

  } else if (statusCode === 201) {
    // ⚠️ CANCELLED by user
    showMessage("Payment was cancelled. You can try again.");

  } else if (statusCode === 202) {
    // ❌ NETWORK / CONNECTIVITY error
    showMessage("Network issue. Check your connection.");

  } else if (statusCode === 205) {
    // ⚠️ SESSION EXPIRED
    showMessage("Session expired. Please restart payment.");
    await reinitializeSession();

  } else if (statusCode === 208) {
    // ❌ AUTHENTICATION FAILED (3DS)
    showMessage("Authentication failed. Try another card.");

  } else {
    // ❌ GENERIC ERROR
    showMessage(`Payment failed (code ${statusCode}). Contact support.`);
  }
}

Digital Wallets — Google Pay™ & Apple Pay

The NCR HPP iframe natively supports digital wallet payments. Wallet buttons are displayed automatically based on backend configuration and onboarding. NCR handles the wallet token exchange entirely within the iframe.

🇬
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 enabled through backend configuration and onboarding — the HPP iframe displays them automatically once activated. The PAYMENT_RETURN and CompleteSession flow is identical to standard card payments — no special client-side handling needed.

Google Pay™

Google Pay usage policy

By enabling Google Pay through the NCR HPP, you agree to the Google Pay API Terms of Service and must comply with the Google Pay API Acceptable Use Policy. Your checkout integration and any associated marketing must also follow the Google Pay Brand Guidelines.

Google Pay is a trademark of Google LLC.

Once Google Pay is enabled for your account by NCR, the Google Pay button appears automatically in the HPP iframe on supported browsers. NCR handles the Google Pay session, token decryption, and payment authorisation entirely within the iframe.

Onboarding required before go-live

Google Pay on HPP uses the Google Pay Direct model. Before going live you must provide NCR with your Google Pay production key pair and onboarding information. NCR configures the keys in the platform and validates processing. Contact your NCR account team to initiate the onboarding process.

curl -X POST "{{baseURL}}/v1.4/PaymentTransactionManagement/InitializeSession" \
  -H "accept: application/json" \
  -H "content-type: application/json" \
  -H "CompanyId: {{CompanyId}}" \
  -H "StoreId: {{StoreId}}" \
  -H "StoreNumber: {{StoreNumber}}" \
  -H "X-Request-ID: {{RequestId}}" \
  -H "X-DateTime: {{DateTime}}" \
  -H "Authorization: {{AuthorizationHeader}}" \
  -d '{
    "Amount": 59.99,
    "ReturnMethod": "Redirect",
    "ReturnURL": "https://your-app.com/payment-return",
    "FrameHostingURL": "https://your-app.com",
    "RequestExpirationUTC": "2026-03-18T12:35:00.000Z",
    "TokenRetrievalRequired": false
  }'
http POST {{baseURL}}/v1.4/PaymentTransactionManagement/InitializeSession \
  accept:application/json \
  content-type:application/json \
  CompanyId:{{CompanyId}} \
  StoreId:{{StoreId}} \
  StoreNumber:{{StoreNumber}} \
  X-Request-ID:{{RequestId}} \
  X-DateTime:{{DateTime}} \
  Authorization:"{{AuthorizationHeader}}" \
  Amount:=59.99 \
  ReturnMethod=Redirect \
  ReturnURL=https://your-app.com/payment-return \
  FrameHostingURL=https://your-app.com \
  RequestExpirationUTC=2026-03-18T12:35:00.000Z \
  TokenRetrievalRequired:=false
import requests

url = "{{baseURL}}/v1.4/PaymentTransactionManagement/InitializeSession"
headers = {
    "accept": "application/json",
    "content-type": "application/json",
    "CompanyId": "{{CompanyId}}",
    "StoreId": "{{StoreId}}",
    "StoreNumber": "{{StoreNumber}}",
    "X-Request-ID": "{{RequestId}}",
    "X-DateTime": "{{DateTime}}",
    "Authorization": "{{AuthorizationHeader}}"
}
payload = {
    "Amount": 59.99,
    "ReturnMethod": "Redirect",
    "ReturnURL": "https://your-app.com/payment-return",
    "FrameHostingURL": "https://your-app.com",
    "RequestExpirationUTC": "2026-03-18T12:35:00.000Z",
    "TokenRetrievalRequired": False
}

response = requests.post(url, headers=headers, json=payload, timeout=30)
print(response.status_code)
print(response.text)
using var client = new HttpClient();

var request = new HttpRequestMessage(HttpMethod.Post,
    "{{baseURL}}/v1.4/PaymentTransactionManagement/InitializeSession");
request.Headers.Add("accept", "application/json");
request.Headers.Add("CompanyId", "{{CompanyId}}");
request.Headers.Add("StoreId", "{{StoreId}}");
request.Headers.Add("StoreNumber", "{{StoreNumber}}");
request.Headers.Add("X-Request-ID", "{{RequestId}}");
request.Headers.Add("X-DateTime", "{{DateTime}}");
request.Headers.Add("Authorization", "{{AuthorizationHeader}}");
request.Content = JsonContent.Create(new {
  Amount = 59.99,
  ReturnMethod = "Redirect",
  ReturnURL = "https://your-app.com/payment-return",
  FrameHostingURL = "https://your-app.com",
  RequestExpirationUTC = "2026-03-18T12:35:00.000Z",
  TokenRetrievalRequired = false
});

using var response = await client.SendAsync(request);
Console.WriteLine((int)response.StatusCode);
Console.WriteLine(await response.Content.ReadAsStringAsync());
HttpClient client = HttpClient.newHttpClient();
String json = """
{
  \"Amount\": 59.99,
  \"ReturnMethod\": \"Redirect\",
  \"ReturnURL\": \"https://your-app.com/payment-return\",
  \"FrameHostingURL\": \"https://your-app.com\",
  \"RequestExpirationUTC\": \"2026-03-18T12:35:00.000Z\",
  \"TokenRetrievalRequired\": false
}
""";

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("{{baseURL}}/v1.4/PaymentTransactionManagement/InitializeSession"))
    .header("accept", "application/json")
    .header("content-type", "application/json")
    .header("CompanyId", "{{CompanyId}}")
    .header("StoreId", "{{StoreId}}")
    .header("StoreNumber", "{{StoreNumber}}")
    .header("X-Request-ID", "{{RequestId}}")
    .header("X-DateTime", "{{DateTime}}")
    .header("Authorization", "{{AuthorizationHeader}}")
    .POST(HttpRequest.BodyPublishers.ofString(json))
    .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://your-app.com/payment-return",
  "FrameHostingURL": "https://your-app.com",
  "RequestExpirationUTC": "2026-03-18T12:35:00.000Z",
  "TokenRetrievalRequired": false
}
<InitializeSessionRequest>
  <Amount>59.99</Amount>
  <ReturnMethod>Redirect</ReturnMethod>
  <ReturnURL>https://your-app.com/payment-return</ReturnURL>
  <FrameHostingURL>https://your-app.com</FrameHostingURL>
  <RequestExpirationUTC>2026-03-18T12:35:00.000Z</RequestExpirationUTC>
  <TokenRetrievalRequired>false</TokenRetrievalRequired>
</InitializeSessionRequest>
{
  "200": {
    "SessionId": "45016f4cce57e5ab4e308a1822b9b9fc3336",
    "RequestURL": "https://.../WebEPS/HPP?session=45016f4cce57e5ab4e308a1822b9b9fc3336"
  },
  "400": {
    "ErrorCode": "INVALID_REQUEST",
    "Message": "Wallet request fields are invalid.",
    "Details": "Verify PaymentMethodType and wallet configuration."
  },
  "401": {
    "ErrorCode": "UNAUTHORIZED",
    "Message": "Authentication failed.",
    "Details": "Invalid signature, merchant credentials, or allowed origin configuration."
  }
}
<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>Wallet request fields are invalid.</Message>
      <Details>Verify wallet configuration.</Details>
    </Error>
  </Status>
  <Status code="401">
    <Error>
      <Code>UNAUTHORIZED</Code>
      <Message>Authentication failed.</Message>
      <Details>Invalid signature, merchant credentials, or allowed origin configuration.</Details>
    </Error>
  </Status>
</Responses>

Apple Pay

Once Apple Pay is activated for your account by NCR, the Apple Pay button appears automatically in the HPP iframe when eligibility conditions are met. The HPP uses the Apple Pay Mass Enablement model — NCR manages the Apple Pay certificate infrastructure (creation, import, and renewal) on your behalf.

Merchant steps before go-live

Before Apple Pay can be activated on your HPP 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 (no redirect, Content-Type: text/plain).
  2. Provide your domain and URL to NCR — contact your NCR account team with your merchant domain and FrameHostingURL. NCR will trigger domain registration with Apple and configure the certificate infrastructure.

Once NCR confirms activation, the Apple Pay button will appear automatically in the HPP iframe when the shopper's browser meets Apple's eligibility requirements.

curl -X POST "{{baseURL}}/v1.4/PaymentTransactionManagement/InitializeSession" \
  -H "accept: application/json" \
  -H "content-type: application/json" \
  -H "CompanyId: {{CompanyId}}" \
  -H "StoreId: {{StoreId}}" \
  -H "StoreNumber: {{StoreNumber}}" \
  -H "X-Request-ID: {{RequestId}}" \
  -H "X-DateTime: {{DateTime}}" \
  -H "Authorization: {{AuthorizationHeader}}" \
  -d '{
    "Amount": 59.99,
    "ReturnMethod": "Redirect",
    "ReturnURL": "https://your-app.com/payment-return",
    "FrameHostingURL": "https://your-app.com",
    "RequestExpirationUTC": "2026-03-18T12:35:00.000Z",
    "TokenRetrievalRequired": false
  }'
http POST {{baseURL}}/v1.4/PaymentTransactionManagement/InitializeSession \
  accept:application/json \
  content-type:application/json \
  CompanyId:{{CompanyId}} \
  StoreId:{{StoreId}} \
  StoreNumber:{{StoreNumber}} \
  X-Request-ID:{{RequestId}} \
  X-DateTime:{{DateTime}} \
  Authorization:"{{AuthorizationHeader}}" \
  Amount:=59.99 \
  ReturnMethod=Redirect \
  ReturnURL=https://your-app.com/payment-return \
  FrameHostingURL=https://your-app.com \
  RequestExpirationUTC=2026-03-18T12:35:00.000Z \
  TokenRetrievalRequired:=false
import requests

url = "{{baseURL}}/v1.4/PaymentTransactionManagement/InitializeSession"
headers = {
    "accept": "application/json",
    "content-type": "application/json",
    "CompanyId": "{{CompanyId}}",
    "StoreId": "{{StoreId}}",
    "StoreNumber": "{{StoreNumber}}",
    "X-Request-ID": "{{RequestId}}",
    "X-DateTime": "{{DateTime}}",
    "Authorization": "{{AuthorizationHeader}}"
}
payload = {
    "Amount": 59.99,
    "ReturnMethod": "Redirect",
    "ReturnURL": "https://your-app.com/payment-return",
    "FrameHostingURL": "https://your-app.com",
    "RequestExpirationUTC": "2026-03-18T12:35:00.000Z",
    "TokenRetrievalRequired": False
}

response = requests.post(url, headers=headers, json=payload, timeout=30)
print(response.status_code)
print(response.text)
using var client = new HttpClient();

var request = new HttpRequestMessage(HttpMethod.Post,
    "{{baseURL}}/v1.4/PaymentTransactionManagement/InitializeSession");
request.Headers.Add("accept", "application/json");
request.Headers.Add("CompanyId", "{{CompanyId}}");
request.Headers.Add("StoreId", "{{StoreId}}");
request.Headers.Add("StoreNumber", "{{StoreNumber}}");
request.Headers.Add("X-Request-ID", "{{RequestId}}");
request.Headers.Add("X-DateTime", "{{DateTime}}");
request.Headers.Add("Authorization", "{{AuthorizationHeader}}");
request.Content = JsonContent.Create(new {
  Amount = 59.99,
  ReturnMethod = "Redirect",
  ReturnURL = "https://your-app.com/payment-return",
  FrameHostingURL = "https://your-app.com",
  RequestExpirationUTC = "2026-03-18T12:35:00.000Z",
  TokenRetrievalRequired = false
});

using var response = await client.SendAsync(request);
Console.WriteLine((int)response.StatusCode);
Console.WriteLine(await response.Content.ReadAsStringAsync());
HttpClient client = HttpClient.newHttpClient();
String json = """
{
  \"Amount\": 59.99,
  \"ReturnMethod\": \"Redirect\",
  \"ReturnURL\": \"https://your-app.com/payment-return\",
  \"FrameHostingURL\": \"https://your-app.com\",
  \"RequestExpirationUTC\": \"2026-03-18T12:35:00.000Z\",
  \"TokenRetrievalRequired\": false
}
""";

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("{{baseURL}}/v1.4/PaymentTransactionManagement/InitializeSession"))
    .header("accept", "application/json")
    .header("content-type", "application/json")
    .header("CompanyId", "{{CompanyId}}")
    .header("StoreId", "{{StoreId}}")
    .header("StoreNumber", "{{StoreNumber}}")
    .header("X-Request-ID", "{{RequestId}}")
    .header("X-DateTime", "{{DateTime}}")
    .header("Authorization", "{{AuthorizationHeader}}")
    .POST(HttpRequest.BodyPublishers.ofString(json))
    .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://your-app.com/payment-return",
  "FrameHostingURL": "https://your-app.com",
  "RequestExpirationUTC": "2026-03-18T12:35:00.000Z",
  "TokenRetrievalRequired": false
}
<InitializeSessionRequest>
  <Amount>59.99</Amount>
  <ReturnMethod>Redirect</ReturnMethod>
  <ReturnURL>https://your-app.com/payment-return</ReturnURL>
  <FrameHostingURL>https://your-app.com</FrameHostingURL>
  <RequestExpirationUTC>2026-03-18T12:35:00.000Z</RequestExpirationUTC>
  <TokenRetrievalRequired>false</TokenRetrievalRequired>
</InitializeSessionRequest>
{
  "200": {
    "SessionId": "45016f4cce57e5ab4e308a1822b9b9fc3336",
    "RequestURL": "https://.../WebEPS/HPP?session=45016f4cce57e5ab4e308a1822b9b9fc3336"
  },
  "400": {
    "ErrorCode": "INVALID_REQUEST",
    "Message": "Wallet request fields are invalid.",
    "Details": "Verify PaymentMethodType and wallet configuration."
  },
  "401": {
    "ErrorCode": "UNAUTHORIZED",
    "Message": "Authentication failed.",
    "Details": "Invalid signature, merchant credentials, or allowed origin configuration."
  }
}
<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>Wallet request fields are invalid.</Message>
      <Details>Verify wallet configuration.</Details>
    </Error>
  </Status>
  <Status code="401">
    <Error>
      <Code>UNAUTHORIZED</Code>
      <Message>Authentication failed.</Message>
      <Details>Invalid signature, merchant credentials, or allowed origin configuration.</Details>
    </Error>
  </Status>
</Responses>
Domain Verification
# 1. Apple requires this file at exactly this path on your domain:
#    https://your-app.com/.well-known/apple-developer-merchantid-domain-association

# 2. Obtain the file from Apple Pay for Developers:
#    https://developer.apple.com/apple-pay/

# 3. Serve it as Content-Type: text/plain (no extension, no redirect)

# Express example:
app.get(
  "/.well-known/apple-developer-merchantid-domain-association",
  (req, res) => {
    res.setHeader("Content-Type", "text/plain");
    res.sendFile(path.join(__dirname, "apple-pay-domain-association"));
  }
);

Apple Pay requirements

The HPP uses the Apple Pay Mass Enablement model — NCR manages Apple certificates and handles registration with Apple on your behalf. Merchant prerequisites are minimal:

  • Provide your merchant domain and merchant URL to your NCR account team
  • Host the NCR-provided domain verification file at: /.well-known/apple-developer-merchantid-domain-association (served as text/plain, no redirects)
  • NCR completes domain registration with Apple and manages the certificate infrastructure
Apple Pay button visibility

The HPP renders the Apple Pay button automatically when the shopper's browser meets Apple's eligibility requirements (Safari on iOS or macOS with a card enrolled in Apple Wallet). No merchant-side browser detection is needed — NCR handles this through the HPP iframe.

Enable all payment methods together

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

JSON
{
  "Amount": 59.99,
  "ReturnMethod": "Redirect",
  "ReturnURL": "https://your-app.com/payment-return",
  "FrameHostingURL": "https://your-app.com",
  "RequestExpirationUTC": "2026-03-18T12:35:00.000Z",
  "AccountAddressCollectionMode": "None",
  "ValidateAccountSecurityCode": false
}

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","GooglePay","ApplePay"] — controls which payment buttons appear
GooglePayMerchantIdstringOptionalNCR-provisioned Google Pay merchant identifier — provided by your NCR account team. Not required for self-registration.
GooglePayMerchantNamestringOptionalDisplay name shown in the Google Pay sheet
ApplePayMerchantIdstringOptionalRequired when PaymentMethodType includes "ApplePay"
ApplePayMerchantNamestringOptionalDisplay name shown in the Apple Pay sheet
ApplePayCountryCodestringOptionalISO 3166-1 alpha-2 (e.g. "US")
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

# Google Pay
GOOGLE_PAY_MERCHANT_ID=YOUR_GOOGLE_MERCHANT_ID
GOOGLE_PAY_MERCHANT_NAME="Your Store Name"

# Apple Pay
APPLE_PAY_MERCHANT_ID=merchant.com.your-app
APPLE_PAY_MERCHANT_NAME="Your Store Name"