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
- Load the SDK in your page and call
ncr.elements()to instantiate the hosted payment form. - Configure payment fields — each field (card number, expiry, CVV, etc.) renders in its own secured iframe hosted by NCR.
- Create session on your server with
InitializeSessionand receive the session details needed to submit. - Submit the form by calling
paymentForm.submit(submissionConfig)with the session details.
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.
- 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.
- Load the HPE SDK and call
ncr.elements()to create the payment form with hosted fields. - Implement the submit flow: InitializeSession →
paymentForm.submit()→ CompleteSession. - Verify success criteria: receive
statusCode = 100and then callCompleteSessiononce.
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.
- 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 RSA Key Generator (.zip)
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.
Download Postman Collection Package (.zip)
Recommended collection flow
- Import
WebEPS API New 2026 for Zioskcollection 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 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.
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 session details to browser |
| Load HPE SDK | Browser | Calls ncr.elements() to render hosted payment fields |
| Payment field iframes | Browser | Each field renders in its own NCR-hosted iframe |
| Submit form | Browser | Calls paymentForm.submit(submissionConfig) with session details |
| CompleteSession | Server | Finalises the transaction — call only after submit resolves successfully |
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 the session details to the browser for use in paymentForm.submit().
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 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.
<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.
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.
<!-- 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.
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);
});
});
});
| Field | R / O | Description |
|---|---|---|
paymentType | Required | Transaction type. Supported value: "CHECKOUT" |
method | Required | Payment method. Supported value: "CARD" |
brands | Optional | List of supported card brands. If empty, all major brands are accepted. |
fields | Required | Field-level configuration including placeholders, formatting, and masking. |
parentElement | Required | ID of the container element where each hosted field iframe is embedded. |
placeholder | Optional | Placeholder text displayed inside the input field. |
enableFormatting | Optional | If true, automatically formats field input (e.g. card number groups). |
masking | Optional | Defines how field values are masked as the user types. |
character | Optional | The masking character (e.g. "*"). |
mode | Optional | Masking 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. |
shrinkLength | Optional | Number of digits to shrink when using a *_SHRINK masking mode. |
cssClassNames | Optional | CSS class names applied for valid / invalid validation states. |
css | Optional | Custom CSS styling rules injected into each hosted field iframe. |
font | Optional | Custom font settings: data (base64), family, format (font/otf, font/ttf, font/woff), and integrity. |
promise | Optional | The promise resolves to an instance of the payment form on success, or an error on failure. |
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.
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
});
});
});
| Field | R / O | Description |
|---|---|---|
uniqueTransactionIdentifier | Required | Unique ID for the transaction session, returned by InitializeSession. |
requestUrl | Required | Payment endpoint URL returned by InitializeSession. |
companyNumber | Required | Merchant company number, appended to the requestUrl. |
fullName | Optional | Cardholder full name for billing. |
address1 | Optional | Primary billing address line. |
address2 | Optional | Secondary address line (e.g., apartment number). |
city | Optional | City of the billing address. |
state | Optional | State or province of the billing address. |
country | Optional | Country of the billing address. |
postalCode | Optional | Postal / ZIP code used for AVS validation. |
isBillingSameAsShipping | Optional | Set to true if the billing address matches the shipping address. |
isDefaultCard | Optional | Flag the card as the default saved card for this profile. |
nickName | Optional | A friendly nickname to identify the saved card. |
tokens | Optional | Array of saved token objects to use for this transaction. |
tokenType | Conditional | Required when tokens are provided. Token type (e.g. "201"). |
tokenValue | Conditional | Required 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.
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>Wallet Integration — Apple Pay & Google Pay
Wallet payment fields (applePay, googlePay) are available from Elements SDK version 1.3.0 and above. Ensure your SDK script URL points to /securepay/elements/1.3.0/sdk.js or later.
The Elements SDK renders native Apple Pay and Google Pay buttons directly inside your page. There is no InitializeSession or CompleteSession step for wallet payments — the wallet handles the payment sheet lifecycle and your code receives an encrypted token through event callbacks. Wallet buttons can appear standalone or combined with card fields in the same ncr.elements() call.
Apple Pay
End-to-end flow
The Apple Pay flow spans three phases across four actors. No card data is entered in your page — Apple manages tokenisation and your backend handles all NCR API calls.
One-time setup
Complete these steps once in the Apple Developer portal before adding the button to your page.
Register a Merchant ID
Go to Certificates, Identifiers & Profiles → Merchant IDs → + and create an identifier such as merchant.com.your-company.shop. You will use this in your backend validation call.
Create a Merchant Identity Certificate
Used for mutual TLS (mTLS) when your server calls Apple's merchant validation URL. Under your Merchant ID → Apple Pay Merchant Identity Certificate → Create Certificate. Download the .cer and convert to .pem + key for use on your server.
openssl x509 -inform DER -in merchant_id.cer -out merchant_id.pem openssl pkcs12 -export -in merchant_id.pem -inkey merchant_id.key -out merchant_id.p12
Create a Payment Processing Certificate
Used to decrypt the Apple Pay payment token on your backend. Under your Merchant ID → Apple Pay Payment Processing Certificate → Create Certificate. Store securely on your payment processing server.
Register and verify your domain
Apple Pay only works on HTTPS domains Apple has verified. Download the Domain Association File from your Merchant ID settings and serve it — with no redirects — at:
https://your-domain.com/.well-known/apple-developer-merchantid-domain-association
Then go to Merchant Domains → Register Domain → Verify. Repeat for every domain (including subdomains) where Apple Pay will appear.
HTML container
<div id="apple-pay-container" style="width:100%;"></div>
SDK configuration
ncr.elements({
data: {
fields: {
applePay: {
parentElement: 'apple-pay-container',
buttonType: 'buy', // 'buy' | 'plain' | 'pay' | 'book' | 'donate'
buttonStyle: 'black', // 'black' | 'white' | 'white-outline'
height: 48, // button height in px
borderRadius: 4,
merchantInfo: {
locale: 'en-US',
merchantIdentifier: 'YOUR_APPLE_PAY_MERCHANT_IDENTIFIER',
merchantName: 'Your Store Name',
merchantCountryCode: 'US',
currencyCode: 'USD',
supportedNetworks: ['visa', 'masterCard', 'amex', 'discover'],
merchantCapabilities: ['supports3DS'],
totalLabel: 'Your Store Name',
totalAmount: '19.99'
}
}
}
},
events: {
onReady: function() { /* SDK ready — hide your spinner */ },
onError: function(error) { /* SDK error — show fallback */ },
onWalletReady: function(provider) { /* Apple Pay button is available */ },
onWalletUnavailable: function(provider, reason) { /* hide button, show alternative */ },
onApplePayValidateMerchant: async function(validationURL, completeFn, abortFn) { /* Step 1 */ },
onApplePayPaymentAuthorized: async function(token, payment, completeFn) { /* Step 2 */ },
onApplePayCancel: function() { /* Step 3 */ }
}
});Event — onApplePayValidateMerchant (Step 1: Merchant validation)
Fires as soon as the Apple Pay sheet opens. Apple verifies your server is a legitimate merchant. You must call completeFn within a few seconds or the sheet closes. This call must be server-side — Apple blocks cross-origin browser requests and requires mTLS using your Merchant Identity Certificate.
Your backend receives the validationURL, POSTs to it with your merchant certificate, and returns Apple's opaque merchantSession JSON unmodified.
onApplePayValidateMerchant: async function(validationURL, completeFn, abortFn) {
try {
const response = await fetch('/your-backend/validate-apple-pay-merchant', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ validationURL })
});
if (!response.ok) throw new Error('Validation failed: ' + response.status);
// Pass Apple's opaque session object directly — do NOT modify it
completeFn(await response.json());
} catch (error) {
abortFn(error.message || 'Merchant validation failed');
}
}// Node.js / Express — server-side
const https = require('https');
const fs = require('fs');
const CERT = fs.readFileSync('/certs/merchant_id.pem');
const KEY = fs.readFileSync('/certs/merchant_id.key');
app.post('/your-backend/validate-apple-pay-merchant', (req, res) => {
const { validationURL } = req.body;
const payload = JSON.stringify({
merchantIdentifier: 'merchant.com.your-company.shop',
domainName: req.hostname, // must match the domain the shopper is on
displayName: 'Your Store Name'
});
const url = new URL(validationURL);
const appleReq = https.request({
hostname: url.hostname, path: url.pathname + url.search, method: 'POST',
cert: CERT, key: KEY,
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) }
}, r => { let b = ''; r.on('data', c => b += c); r.on('end', () => res.json(JSON.parse(b))); });
appleReq.on('error', e => res.status(500).json({ error: e.message }));
appleReq.end(payload);
});# Python / Flask — server-side
import requests
from flask import request, jsonify
CERT = '/certs/merchant_id.pem'
KEY = '/certs/merchant_id.key'
@app.route('/your-backend/validate-apple-pay-merchant', methods=['POST'])
def validate_merchant():
validation_url = request.json['validationURL']
payload = {
'merchantIdentifier': 'merchant.com.your-company.shop',
'domainName': request.host, # must match the shopper's domain
'displayName': 'Your Store Name'
}
r = requests.post(validation_url, json=payload, cert=(CERT, KEY)) # mTLS
r.raise_for_status()
return jsonify(r.json()) # return Apple's session object unmodifiedEvent — onApplePayPaymentAuthorized (Step 2: Payment processing)
Fires after the shopper authenticates with Face ID / Touch ID. You have ~30 seconds to process the payment and call completeFn. Send the full payment object to your backend — it contains the encrypted token (payment.token.paymentData) and billing contact. Your backend decrypts it using the Payment Processing Certificate and submits to your acquirer.
completeFn({ status: 'success' }) — shows a green checkmark and closes the sheet.
completeFn({ status: 'failure', errors: [{ message: '...' }] }) — shows an error; sheet stays open for retry.
onApplePayPaymentAuthorized: async function(token, payment, completeFn) {
try {
// Send the full payment object — backend needs billingContact AND the encrypted token
const response = await fetch('/your-backend/process-apple-pay-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ payment })
});
if (!response.ok) throw new Error('Payment request failed: ' + response.status);
const result = await response.json();
const isSuccess = result.success === true || result.status === 'success';
completeFn(isSuccess
? { status: 'success' }
: { status: 'failure', errors: result.errors || [{ message: result.message || 'Payment declined' }] }
);
} catch (error) {
completeFn({ status: 'failure', errors: [{ message: error.message || 'Payment failed' }] });
}
}# Python / Flask — server-side
@app.route('/your-backend/process-apple-pay-payment', methods=['POST'])
def process_apple_pay():
payment = request.json['payment']
# POST to NCR Payment Capture API with session headers
headers = {
'Content-Type': 'application/json',
'X-SessionId': session_id, # from InitializeExternalSession
'X-Company': company_id,
'X-UniqueTransactionIdentifier': unique_tx_id
}
payload = {
'paymentType': { 'sourceType': 'APPLEPAY' },
'sources': [{
'sourceType': 'APPLEPAY',
'encryptionData': {
'encryptionType': 'RSA',
'encryptionTarget': 'WEB',
'encryptionBlock': json.dumps(payment) # full payment object as string
}
}]
}
resp = requests.post(
f'{NCR_BASE_URL}/Ecommerce/SecurePay/Payment/Capture',
json=payload, headers=headers
)
resp.raise_for_status()
result = resp.json()
if result.get('success') and result.get('data', {}).get('statusCode') == 100:
# Call CompleteSession to finalise the transaction
complete_session(session_id)
return jsonify({ 'success': True })
return jsonify({ 'success': False, 'message': 'Capture failed' })Event — onApplePayCancel (Step 3: Cancellation)
Fires when the shopper dismisses the sheet without authorising. Use it to reset any pending order state.
onApplePayCancel: function() {
console.log('[Apple Pay] User cancelled');
// Reset any pending order state
}Apple Pay events summary
| Event | Parameters | When it fires | Your action |
|---|---|---|---|
onReady |
none | SDK has initialised and the Apple Pay button is rendered in the DOM. | Hide any loading skeleton; make the payment section visible. |
onError |
(error) |
SDK or runtime initialisation error. | Show a fallback payment method; log error for support diagnostics. |
onWalletReady |
(provider) |
Apple Pay confirmed as available — device is capable and has saved cards. | Optionally reveal additional Apple Pay UI. provider is "applePay". |
onWalletUnavailable |
(provider, reason) |
Apple Pay unavailable (non-Safari browser, no saved cards, or device not supported). | Hide the Apple Pay button; show an alternative payment option. Log reason if needed. |
onApplePayValidateMerchant |
(validationURL, completeFn, abortFn) |
Apple Pay sheet is opening — Apple requests server-side merchant validation. | POST validationURL to your backend → receive merchant session → call completeFn(session). Must complete within a few seconds. Call abortFn(msg) on error. |
onApplePayPaymentAuthorized |
(token, payment, completeFn) |
Shopper authenticated with Face ID / Touch ID. Payment token is ready to process. | POST payment object to your backend for decryption and acquirer submission. Call completeFn({ status: 'success' }) or completeFn({ status: 'failure', errors: [...] }). Must complete within ~30 s. |
onApplePayCancel |
none | Shopper dismissed the Apple Pay sheet without authorising. | Reset any pending order state your UI created before the sheet opened. |
Apple Pay testing
Apple Pay only works in Safari on macOS or iOS — it is unavailable in Chrome or Firefox. Your domain must be registered with Apple even for testing. Use a device enrolled in Apple's Sandbox environment with test cards added to Wallet. See Apple Sandbox Testing for setup.
Google Pay
End-to-end flow
Google Pay has no merchant validation step — the shopper authenticates entirely inside Google's sheet and the SDK fires a callback with the encrypted token, which your backend submits to the NCR gateway.
One-time setup
Google Pay merchant account
Register at pay.google.com/business/console. You receive a Merchant ID once approved for production. A Merchant ID is not required in TEST environment.
Gateway configuration
Your NCR account team provides the gateway identifier (e.g. ncrpaymentsolutions) and gatewayMerchantId for your account. These go in tokenizationParameters inside the SDK data config.
HTML container
<div id="google-pay-container" style="width:100%;"></div>
SDK configuration
ncr.elements({
data: {
fields: {
googlePay: {
parentElement: 'google-pay-container',
environment: 'TEST', // 'TEST' or 'PRODUCTION'
merchantId: 'YOUR_GOOGLE_MERCHANT_ID', // omit in TEST
merchantName: 'Your Store Name',
currencyCode: 'USD',
countryCode: 'US',
totalPriceStatus: 'FINAL',
totalPrice: '19.99',
buttonType: 'buy', // 'buy' | 'pay' | 'plain' | 'book' | 'donate'
buttonColor: 'default', // 'default' | 'black' | 'white'
tokenizationParameters: {
gateway: 'ncrpaymentsolutions',
gatewayMerchantId: 'YOUR_GATEWAY_MERCHANT_ID'
}
}
}
},
events: {
onReady: function() { /* SDK ready */ },
onError: function(error) { /* SDK error */ },
onWalletReady: function(provider) { /* Google Pay button available */ },
onWalletUnavailable: function(provider, reason) { /* hide button */ },
onGooglePayPaymentAuthorized: async function(paymentData, completeFn) { /* Step 1 */ },
onGooglePayCancel: function() { /* Step 2 */ }
}
});Event — onGooglePayPaymentAuthorized (Step 1: Payment processing)
Fires after the shopper selects a card and confirms in the Google Pay sheet. The key field is paymentData.paymentMethodData.tokenizationData.token — send this to your gateway for decryption and authorisation.
completeFn({ transactionState: 'SUCCESS' }) — Google confirms and closes the sheet.
completeFn({ transactionState: 'ERROR' }) — Google shows an error to the shopper.
onGooglePayPaymentAuthorized: async function(paymentData, completeFn) {
try {
const response = await fetch('/your-backend/process-google-pay-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ paymentData })
});
if (!response.ok) throw new Error('Payment request failed: ' + response.status);
const result = await response.json();
const isSuccess = result.success === true || result.status === 'success';
if (typeof completeFn === 'function') completeFn({ transactionState: isSuccess ? 'SUCCESS' : 'ERROR' });
return { transactionState: isSuccess ? 'SUCCESS' : 'ERROR' };
} catch (error) {
const out = { transactionState: 'ERROR' };
if (typeof completeFn === 'function') completeFn(out);
return out;
}
}# Python / Flask — server-side
@app.route('/your-backend/process-google-pay-payment', methods=['POST'])
def process_google_pay():
payment_data = request.json['paymentData']
# Extract the encrypted token from the Google Pay payload
token = payment_data['paymentMethodData']['tokenizationData']['token']
# POST to NCR Payment Capture API with session headers
headers = {
'Content-Type': 'application/json',
'X-SessionId': session_id, # from InitializeExternalSession
'X-Company': company_id,
'X-UniqueTransactionIdentifier': unique_tx_id
}
payload = {
'paymentType': { 'sourceType': 'GOOGLEPAY' },
'sources': [{
'sourceType': 'GOOGLEPAY',
'encryptionData': {
'encryptionType': 'RSA',
'encryptionTarget': 'WEB',
'encryptionBlock': json.dumps(token) # tokenizationData.token as string
}
}]
}
resp = requests.post(
f'{NCR_BASE_URL}/Ecommerce/SecurePay/Payment/Capture',
json=payload, headers=headers
)
resp.raise_for_status()
result = resp.json()
if result.get('success') and result.get('data', {}).get('statusCode') == 100:
# Call CompleteSession to finalise the transaction
complete_session(session_id)
return jsonify({ 'success': True })
return jsonify({ 'success': False, 'message': 'Capture failed' })Event — onGooglePayCancel (Step 2: Cancellation)
onGooglePayCancel: function() {
console.log('[Google Pay] User cancelled');
}Google Pay events summary
| Event | Parameters | When it fires | Your action |
|---|---|---|---|
onReady |
none | SDK initialised and the Google Pay button is rendered in the DOM. | Hide any loading skeleton; make the payment section visible. |
onError |
(error) |
SDK or runtime initialisation error. | Show a fallback payment method; log error for support diagnostics. |
onWalletReady |
(provider) |
Google Pay confirmed as available on this device. provider is "googlePay". |
Optionally reveal additional Google Pay UI cues. |
onWalletUnavailable |
(provider, reason) |
Google Pay unavailable on this browser or device. | Hide the Google Pay button; show an alternative. In TEST mode this never fires. |
onGooglePayPaymentAuthorized |
(paymentData, completeFn) |
Shopper selected a card and confirmed. Encrypted token is ready to process. | POST paymentData to your backend; gateway decrypts paymentData.paymentMethodData.tokenizationData.token. Call completeFn({ transactionState: 'SUCCESS' }) or completeFn({ transactionState: 'ERROR' }). |
onGooglePayCancel |
none | Shopper closed the Google Pay sheet without completing payment. | Reset any pending order state. |
Google Pay testing
Set environment: 'TEST' in the SDK config. TEST mode shows a mock card that produces a simulated encrypted token — no real card required. Switch to 'PRODUCTION' with your approved merchantId before going live. Review the Google Pay integration checklist before launching.
Payment Capture API — Apple Pay & Google Pay
After the shopper authorises payment, your backend calls the NCR Payment Capture endpoint to submit the encrypted wallet token. This single endpoint handles both Apple Pay and Google Pay — only the sourceType and encryptionBlock content differ.
All three headers are received from your session initialisation step and must be included in the Capture request:
| Header | Description |
|---|---|
X-SessionId | Session identifier returned by InitializeExternalSession |
X-Company | Merchant company identifier |
X-UniqueTransactionIdentifier | Unique transaction reference for this payment |
Endpoint
POST /Ecommerce/SecurePay/Payment/Capture
Request payload
{
"paymentType": {
"sourceType": "APPLEPAY"
},
"sources": [{
"sourceType": "APPLEPAY",
"encryptionData": {
"encryptionType": "RSA",
"encryptionTarget": "WEB",
"encryptionBlock": "<stringified Apple Pay payment object>"
}
}]
}
// encryptionBlock: JSON.stringify(payment)
// where 'payment' is the full object from onApplePayPaymentAuthorized —
// includes payment.token.paymentData AND payment.billingContact{
"paymentType": {
"sourceType": "GOOGLEPAY"
},
"sources": [{
"sourceType": "GOOGLEPAY",
"encryptionData": {
"encryptionType": "RSA",
"encryptionTarget": "WEB",
"encryptionBlock": "<stringified Google Pay token>"
}
}]
}
// encryptionBlock: JSON.stringify(paymentData.paymentMethodData.tokenizationData.token)
// where 'paymentData' is the object from onGooglePayPaymentAuthorizedResponse
{
"success": true,
"data": {
"sessionId": "<session-id>",
"statusCode": 100
}
}
// statusCode 100 = payment authorised and captured successfully400 Invalid encryption data — wallet token is missing or malformed 401 Session expired — session is no longer valid; re-initialise 403 Merchant not authorised — wallet not enabled or configured for this account 404 Session ID not found — session or endpoint is invalid 500 Internal server error — general error; contact NCR support
Complete the session
After a successful capture response (statusCode: 100), finalise the transaction by calling CompleteSession. This is identical to the standard card payment flow:
POST /v1.4/PaymentTransactionManagement/CompleteSession
{
"SessionId": "<session-id>",
"RequestExpirationUtc": "2026-08-28T12:00:00.000Z"
}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"] — controls which payment buttons appear |
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