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
- Create session on your server with
InitializeSessionand receiveRequestURL. - Show hosted checkout in your UI by loading
RequestURLin an iframe and listening for payment return events. - Complete payment on your server with
CompleteSessiononce the return status indicates success.
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.
- Generate RSA keys and keep private key material secure. See RSA key generation.
- Validate with Postman first so signing and headers are confirmed before app coding. See Postman end-to-end.
- Use the API explorer reference to inspect headers, payloads, and language snippets: Open HPP API Explorer.
- Implement iframe flow: InitializeSession → iframe → postMessage → CompleteSession.
- Verify success criteria: receive
statusCode = 100and then callCompleteSessiononce.
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.
- Generate RSA keys first and secure private key material. Follow RSA key generation (clean setup).
- Prepare Postman environment with
privateEncryptedKeyPEMandpasswordto validate signatures before app integration. - Confirm NCR onboarding values:
X-CompanyNumber,X-StoreNumber, and allowed origins forReturnURL/FrameHostingURL. - Set server environment variables and keep private key usage server-side only.
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
- Open
RsaKeyGenerationStudio.exefrom the Dev Portal package. - Enter key prefix (for example:
KC250630-STORE190). - Use key size
2048or4096(recommended default:2048). - Set a key secret so private PEM is exported as encrypted PKCS#8.
- Generate and verify these files:
{Prefix}.Private.pem,{Prefix}.Public.pem,{Prefix}.Private.xml,{Prefix}.Public.xml.
Where each key file is used
| File | Used by | Purpose |
|---|---|---|
{Prefix}.Private.pem | Postman (privateEncryptedKeyPEM) | RSA signing for InitializeSession / CompleteSession scripts |
{Prefix}.Public.pem | Postman optional verification | Signature verification during troubleshooting |
{Prefix}.Public.xml | Mobile registration body | ClientPublicKey for RegisterMobileDevice |
{Prefix}.Private.xml | Legacy tools only | Not required in standard HPP browser iframe flow |
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
- Import
NCR Postman WebEPS APIcollection and matching environment. - Set environment values:
baseURL,API Version,Company Number,Store Number,Client Application Name. - Set key material:
privateEncryptedKeyPEMandpassword. - Run InitializeSession request (pre-request script injects
RequestExpirationUTCandX-Signature). - Open returned
RequestURLin browser and complete payment. - Run CompleteSession using saved
CompleteSessioncollection variable.
Need a guided API explorer with grouped endpoints and multi-language snippets? Click below to open and explore.
Open HPP API ExplorerArchitecture
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.
InitializeSession and CompleteSession must be called from your Application Server. Your NCR private key and company credentials must never be exposed client-side.
| Step | Where it runs | Description |
|---|---|---|
| InitializeSession | Server | Signs and POSTs to NCR; returns SessionId + RequestURL to browser |
| Load iframe | Browser | Sets <iframe src="{RequestURL}"> |
| postMessage events | Browser | Listen for PAGE_LOAD_COMPLETE, IFRAME_RESIZE, PAYMENT_RETURN |
| ReturnURL page | Browser | Posts statusCode back to parent via postMessage |
| CompleteSession | Server | Finalises the transaction — call only after statusCode === 100 |
Integration flow
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
| Header | Required | Description |
|---|---|---|
Content-Type | Yes | application/json or text/xml |
X-CompanyNumber | Yes | Merchant company number |
X-StoreNumber | Yes | Merchant store number |
X-ReferenceId | Yes | Unique correlation ID per request |
X-Client-Application-Name | Yes | Client integration identifier |
X-Client-Application-Version | Optional | Client application version |
X-Signature | Yes | RSA-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:=trueimport 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.
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
| Event | Payload | Action |
|---|---|---|
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.
Use a useRef flag (completingRef.current) to ensure CompleteSession is invoked exactly once per session, even in React StrictMode.
Required headers
| Header | Required | Description |
|---|---|---|
Content-Type | Yes | application/json or text/xml |
X-CompanyNumber | Yes | Merchant company number |
X-StoreNumber | Yes | Merchant store number |
X-ReferenceId | Yes | Unique correlation ID per request |
X-Signature | Yes | RSA-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.000Zimport 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.
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.
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™
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.
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:=falseimport 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.
Before Apple Pay can be activated on your HPP you must:
- 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). - 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:=falseimport 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># 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 astext/plain, no redirects) - NCR completes domain registration with Apple and manages the certificate infrastructure
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.
{
"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
| Header | Example value | Description |
|---|---|---|
Content-Type | application/json | Always required |
X-CompanyNumber | YOUR_COMPANY_NUMBER | Your NCR company number |
X-StoreNumber | YOUR_STORE_NUMBER | Your NCR store number |
X-ReferenceId | 32-char hex | Unique per request — crypto.randomUUID().replace(/-/g,"") |
X-Client-Application-Name | YourAppName | Identifier for your integration |
X-Client-Application-Version | 1.0.0 | Your application version string |
X-Signature | Uppercase hex | RSA-SHA256 signature of the full serialised request body — server-side only |
API Reference — Request body fields
| Field | Type | Required | Description |
|---|---|---|---|
Amount | number | Required | Total charge in dollars (e.g. 59.99) |
ReturnURL | string | Required | Your page that receives statusCode after redirect |
FrameHostingURL | string | Required | Origin of the page hosting the iframe (for postMessage trust) |
ReturnMethod | string | Required | "Redirect" — iframe navigates to ReturnURL on completion |
RequestExpirationUTC | string (ISO) | Required | Session expiry — typically 5 min from now |
PaymentMethodType | string[] | Optional | ["CreditCard","GooglePay","ApplePay"] — controls which payment buttons appear |
GooglePayMerchantId | string | Optional | NCR-provisioned Google Pay merchant identifier — provided by your NCR account team. Not required for self-registration. |
GooglePayMerchantName | string | Optional | Display name shown in the Google Pay sheet |
ApplePayMerchantId | string | Optional | Required when PaymentMethodType includes "ApplePay" |
ApplePayMerchantName | string | Optional | Display name shown in the Apple Pay sheet |
ApplePayCountryCode | string | Optional | ISO 3166-1 alpha-2 (e.g. "US") |
AuthorizationMode | string | Optional | "Sale" (purchase flow) |
TokenRetrievalRequired | boolean | Optional | Return a payment token for future use |
AccountAddressCollectionMode | string | Optional | "Full" | "PostalCodeOnly" | "None" |
ValidateAccountSecurityCode | boolean | Optional | Require CVV entry (set false for wallet payments) |
ValidateAccountAddress | boolean | Optional | Require billing address in the iframe |
CardType | object | Optional | { Type: "Credit"|"Debit", Name: "Visa"|"MasterCard"… } |
TimeoutInMinutes | number | null | Optional | Session timeout override (default 5 min) |
AllowCardNaming | boolean | Optional | Allow customer to name/save their card |
Status code reference
| Code | Label | Description | Recommended action |
|---|---|---|---|
100 | ✅ Success | Payment completed successfully | Call CompleteSession → navigate to confirmation |
201 | ⚠️ Cancelled | Customer cancelled the payment | Show retry option |
202 | ❌ Network Error | Connectivity issue during transaction | Ask user to retry |
203 | ❌ Server Reject | Transaction rejected by NCR server | Log & contact support |
204 | ❌ Technical Error | Internal technical issue | Retry or contact support |
205 | ⚠️ Session Expired | Payment session has expired (> 5 min) | Re-initialise session |
206 | ❌ External Error | Error in an external system | Retry later |
207 | ❌ Invalid Request | Malformed or invalid request body | Check request fields |
208 | ❌ Auth Failed | Authentication / 3DS failed | Ask for a different card |
209 | ⚠️ Timeout | Payment request timed out | Retry |
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
Use the same signed-header model as InitializeSession and CompleteSession. Keep private keys and merchant credentials out of browser code.
Required headers
| Header | Required | Description |
|---|---|---|
Content-Type | Yes | application/json or text/xml |
X-CompanyNumber | Yes | Merchant company number |
X-StoreNumber | Yes | Merchant store number |
X-ReferenceId | Yes | Unique correlation ID per request |
X-Signature | Yes | RSA-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.000Zimport 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
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"