Autenticación y Cifrado de Requests
Cuando encrypted_http_communication está habilitado en tu terminal, todos los requests hacia Kushki ONE Connect deben ir firmados y cifrados. El payload original nunca viaja en texto plano.
El equipo de Kushki activa esta configuración durante el onboarding. Confirma con tu contacto técnico si aplica para tu integración antes de implementar este flujo.
Variables requeridas
Asegúrate de tener estas cuatro variables disponibles en tu cliente:
| Variable | Descripción |
|---|---|
businessCode | Llave privada que autentica a tu comercio |
terminalSerial | Número de serie de la terminal SmartPOS |
timestamp | Unix timestamp en segundos (UTC) |
requestData | Payload original del request |
Flujo general
Cada request pasa por dos procesos independientes que parten del mismo requestData. Uno produce la firma y el otro produce el payload cifrado. Ambos se envían juntos en el request final.
Cadena de derivación de claves
Antes de implementar los pasos, revisa cómo se relacionan tus inputs con las claves finales. Las tres variables de entrada (businessCode, terminalSerial, timestamp) producen exactamente dos outputs: aesKey para el cifrado y encodedKeyTimestamp para la firma.
El password es el valor central de esta cadena. Cambia cada minuto porque depende de formattedDate, lo que hace que cualquier request firmado caduque automáticamente.
Paso 1 — Genera el timestamp
Guarda este valor en una variable al inicio del flujo. Lo reutilizarás en los pasos 3, 4 y 6.
const timestamp = Math.floor(Date.now() / 1000);// Ejemplo: 1710000000
Paso 2 — Genera 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(':');}// Ejemplo de salida: "2026:03:24:21:55"
Paso 3 — Genera el password temporal
Este valor cambia cada minuto porque depende de formattedDate.
const token = businessCode + terminalSerial;const base = token + formattedDate;const key = base.padEnd(32, '0');const password = MD5(key); // string hexadecimal de 32 caracteres
Paso 4 — Construye dataWithKey (solo para la firma)
dataWithKey es una copia de requestData con el campo key añadido. No se cifra ni se envía.
const encodedKeyTimestamp = Base64(password + timestamp);const dataWithKey = {...requestData,key: encodedKeyTimestamp};
Paso 5 — Genera la firma (Authorization)
const json = JSON.stringify(dataWithKey);const dataJson = Base64(json);const hash = SHA512(dataJson); // hex string
Headers resultantes:
Authorization: Basic <hash>timestamp: <timestamp>
Paso 6 — Cifra el payload (AES-256-CBC)
Solo se cifra requestData (sin key).
const aesKey = (timestamp + "___" + password).substring(0, 32);const iv = randomBytes(16); // padding PKCS7const encrypted = AES_CBC(JSON.stringify(requestData), aesKey, iv);const data = iv.hex + ':' + encrypted.hex;// Ejemplo: "a3f1...b2c4:9e0d...7f21"
Paso 7 — Arma el request final
La estructura del request varía según el método HTTP. En ambos casos los headers son iguales; lo que cambia es cómo se entrega el campo data.
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>
Elimina los demás query params del request original antes de enviarlo.
Errores de autenticación
Si la firma es inválida, recibirás:
{"type": "AUTH","code": "UNAUTHORIZED","message": "Authorization signature is invalid"}
Las tres causas más frecuentes tienen solución directa. Usa este árbol de diagnóstico para identificar cuál aplica a tu caso:
Script de referencia para Postman
Configura este Pre-request Script a nivel de colección para que el flujo de firma y cifrado se ejecute automáticamente en cada request.
Variables requeridas:
| Variable | Valor |
|---|---|
businessCode | Llave privada de tu comercio |
terminalSerial | Serial de tu terminal |
encrypted_http_communication | true para activar el flujo |
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();
Realiza cobros con Kushki One
Con la autenticación lista, revisa el flujo completo de cobro semi-integrado.
Códigos de error
Consulta el catálogo completo de errores, incluyendo los de autenticación fallida.
Chile
Colombia
Ecuador
Mexico