Autenticación y Cifrado de Requests
When encrypted_http_communication is enabled on your terminal, all requests to Kushki ONE Connect must be signed and encrypted. The original payload never travels in plain text.
The Kushki team activates this setting during onboarding. Confirm with your technical contact whether it applies to your integration before implementing this flow.
Required variables
Make sure you have these four variables available in your client:
| Variable | Description |
|---|---|
businessCode | Private key that authenticates your merchant account |
terminalSerial | Serial number of the SmartPOS terminal |
timestamp | Unix timestamp in seconds (UTC) |
requestData | Original request payload |
General flow
Each request goes through two independent processes that start from the same requestData. One produces the signature and the other produces the encrypted payload. Both are sent together in the final request.
Key derivation chain
Before implementing the steps, review how your inputs relate to the final keys. The three input variables (businessCode, terminalSerial, timestamp) produce exactly two outputs: aesKey for encryption and encodedKeyTimestamp for signing.
The password is the central value in this chain. It changes every minute because it depends on formattedDate, which causes any signed request to expire automatically.
Step 1 — Generate the timestamp
Store this value in a variable at the start of the flow. You will reuse it in steps 3, 4, and 6.
const timestamp = Math.floor(Date.now() / 1000);// Example: 1710000000
Step 2 — Generate formattedDate (UTC)
function unixTimestampToFormattedDate(unixTimestamp) {const date = new Date(unixTimestamp * 1000);const pad = (n) => String(n).padStart(2, '0');return [date.getUTCFullYear(),pad(date.getUTCMonth() + 1),pad(date.getUTCDate()),pad(date.getUTCHours()),pad(date.getUTCMinutes())].join(':');}// Example output: "2026:03:24:21:55"
Step 3 — Generate the temporary password
This value changes every minute because it depends on formattedDate.
const token = businessCode + terminalSerial;const base = token + formattedDate;const key = base.padEnd(32, '0');const password = MD5(key); // 32-character hexadecimal string
Step 4 — Build dataWithKey (for signing only)
dataWithKey is a copy of requestData with the key field added. It is not encrypted or sent.
const encodedKeyTimestamp = Base64(password + timestamp);const dataWithKey = {...requestData,key: encodedKeyTimestamp};
Step 5 — Generate the signature (Authorization)
const json = JSON.stringify(dataWithKey);const dataJson = Base64(json);const hash = SHA512(dataJson); // hex string
Resulting headers:
Authorization: Basic <hash>timestamp: <timestamp>
Step 6 — Encrypt the payload (AES-256-CBC)
Only requestData is encrypted (without key).
const aesKey = (timestamp + "___" + password).substring(0, 32);const iv = randomBytes(16); // PKCS7 paddingconst encrypted = AES_CBC(JSON.stringify(requestData), aesKey, iv);const data = iv.hex + ':' + encrypted.hex;// Example: "a3f1...b2c4:9e0d...7f21"
Step 7 — Assemble the final request
The request structure varies depending on the HTTP method. In both cases the headers are the same; what changes is how the data field is delivered.
POST · PATCH · PUT
Headers
Authorization: Basic <hash>timestamp: <timestamp>Content-Type: application/json
Body
{"data": "<iv_hex>:<ciphertext_hex>"}
GET
Headers
Authorization: Basic <hash>timestamp: <timestamp>
Query param
GET /endpoint?data=<iv_hex>:<ciphertext_hex>
Remove all other query params from the original request before sending it.
Authentication errors
If the signature is invalid, you will receive:
{"type": "AUTH","code": "UNAUTHORIZED","message": "Authorization signature is invalid"}
The three most common causes each have a direct fix. Use this decision tree to identify which one applies to your case:
Postman reference script
Configure this Pre-request Script at the collection level so the signing and encryption flow runs automatically on every request.
Required variables:
| Variable | Value |
|---|---|
businessCode | Your merchant private key |
terminalSerial | Your terminal serial number |
encrypted_http_communication | true to enable the flow |
function getCurrentTimestamp() {return Math.floor(new Date().getTime() / 1000);}function unixTimestampToFormattedDate(ts) {const d = new Date(ts * 1000);const p = (n) => String(n).padStart(2, '0');return `${d.getUTCFullYear()}:${p(d.getUTCMonth()+1)}:${p(d.getUTCDate())}:${p(d.getUTCHours())}:${p(d.getUTCMinutes())}`;}function generateTokenPassword(token, ts) {const CryptoJS = require('crypto-js');const key = (token + unixTimestampToFormattedDate(ts)).padEnd(32, '0');return CryptoJS.MD5(key).toString();}function encryptData(text, ts, terminalSerial) {const CryptoJS = require('crypto-js');const password = generateTokenPassword(pm.variables.get("businessCode") + terminalSerial, ts);const key = (ts + "___" + password).substring(0, 32);const iv = CryptoJS.lib.WordArray.random(16);const enc = CryptoJS.AES.encrypt(text, CryptoJS.enc.Utf8.parse(key), {iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7});return iv.toString(CryptoJS.enc.Hex) + ':' + enc.ciphertext.toString(CryptoJS.enc.Hex);}function buildAuthenticationHash(data, ts, terminalSerial) {const CryptoJS = require('crypto-js');const password = generateTokenPassword(pm.variables.get("businessCode") + terminalSerial, ts);const encodedKeyTimestamp = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(password + ts));data.key = encodedKeyTimestamp;const dataJson = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(JSON.stringify(data)));return CryptoJS.SHA512(dataJson).toString(CryptoJS.enc.Hex);}function executeScript() {const terminalSerial = pm.variables.get('terminalSerial');const businessCode = pm.variables.get('businessCode');if (!terminalSerial) throw new Error("terminalSerial is not set");if (!businessCode) throw new Error("businessCode is not set");const ts = getCurrentTimestamp();let requestData = {};try {if (pm.request.body?.mode === 'raw' && pm.request.body.raw)requestData = JSON.parse(pm.request.body.raw);} catch (e) {}if (pm.request.method === 'GET')pm.request.url.query.all().forEach((p) => { if (p.key !== 'data') requestData[p.key] = p.value; });delete requestData.key;const hash = buildAuthenticationHash(structuredClone(requestData), ts, terminalSerial);const encryptedData = encryptData(JSON.stringify(requestData), ts, terminalSerial);pm.request.headers.add({ key: 'Authorization', value: `Basic ${hash}` });pm.request.headers.add({ key: 'timestamp', value: ts.toString() });pm.request.headers.upsert({ key: 'Content-Type', value: 'application/json' });if (pm.request.method === 'GET') {pm.request.url.query.add({ key: 'data', value: encryptedData });pm.request.url.query.members.forEach((p) => { if (p.key !== 'data') p.disabled = true; });} else {pm.request.body.mode = 'raw';pm.request.body.raw = JSON.stringify({ data: encryptedData });}}const raw = pm.variables.get('encrypted_http_communication');const httpEncrypted = raw === true || String(raw).toLowerCase() === 'true' || raw === 1 || raw === '1';if (httpEncrypted) executeScript();
Charge with Kushki ONE
With authentication ready, review the complete semi-integrated charge flow.
Error codes
Check the full error catalog, including authentication failure codes.
Chile
Colombia
Ecuador
Peru