Payday Public API
    • Proceso de Venta
    • Autenticacion y Firma HMAC
    • Proceso de Venta
      • Cotizacion - crear
        POST
      • Referencias - solicitar
        POST
      • Ofertas - previsualizar
        POST
      • Ofertas - seleccionar
        POST
      • Contrato - renderizar
        POST
      • Contrato - firma
        POST
      • Validar ICCID
        POST
      • Validar IMEI
        POST
      • Validar ICCID + IMEI
        POST
      • Finalizar venta
        POST
    • Productos
      • Marcas
        GET
      • Modelos y Prefijos por marca
        GET
    • Medios de Notificacion
      • Registrar Webhook para cotizacion
        POST
      • Polling Cotizacion
        GET
      • Historial de Eventos
        GET
    • Eventos Webhook
      • Evento quote.updated
      • reference.requested
      • reference.accepted
      • imei.validated
      • contract.signed
      • offer.selected
      • offer.previewed
    • Health check
      GET

    Autenticacion y Firma HMAC

    Public API - Autenticacion y Firma HMAC#

    Este documento explica a detalle como autenticar requests hacia la Public API de Payday, como construir la firma HMAC correctamente, que se firma exactamente, y como implementar helpers reutilizables en distintos lenguajes.
    Su objetivo es resolver las dudas mas comunes de integracion:
    1.
    Que headers son obligatorios.
    2.
    Que valor usa cada header.
    3.
    Como construir el canonical string.
    4.
    Que body se debe hashear.
    5.
    Como firmar requests en Node.js, Python, PowerShell, C#, Bash y Postman/Apidog.
    6.
    Como diagnosticar errores como INVALID_SIGNATURE o REPLAY_DETECTED.

    1. Alcance de esta autenticacion#

    Las rutas bajo:
    /public-api/v1/...
    usan una cadena de seguridad en este orden:
    1.
    Validacion de X-Api-Key.
    2.
    Validacion de IP whitelist, si la key tiene whitelist configurada.
    3.
    Validacion HMAC con X-Timestamp, X-Nonce y X-Signature.
    4.
    Proteccion anti replay por nonce.
    5.
    Rate limit por API key.

    2. Credenciales que usa la firma#

    Cuando se crea o rota una API key, el backend devuelve credenciales que se muestran una sola vez.
    Para firmar requests necesitas estas dos:
    1.
    CLIENT_ID: se envia en X-Api-Key.
    2.
    HMAC_SECRET: se usa para calcular X-Signature.
    Importante:
    1.
    clientSecret no participa en la firma HMAC.
    2.
    HMAC_SECRET debe almacenarse en un backend o entorno confiable.
    3.
    No debes exponer HMAC_SECRET en frontend publico, apps web abiertas o codigo cliente distribuido.

    3. Headers obligatorios#

    Toda request a /public-api/v1/... debe incluir:
    Descripcion de cada header:
    HeaderDescripcion
    X-Api-KeyIdentificador publico de la API key.
    X-TimestampTimestamp Unix en milisegundos.
    X-NonceValor unico por request, normalmente UUID v4.
    X-SignatureFirma HMAC SHA-256 en hexadecimal.
    Content-TypeRecomendado application/json para requests con body JSON.

    4. Reglas exactas de la firma#

    4.1 Canonical string#

    La firma se calcula sobre este canonical string:
    METHOD
    PATH_Y_QUERY
    TIMESTAMP
    NONCE
    BODY_HASH
    La funcion real del backend construye:
    ${method.toUpperCase()}\n${path}\n${timestamp}\n${nonce}\n${bodyHash}

    4.2 Que valor va en PATH_Y_QUERY#

    Debes firmar solo:
    1.
    El pathname.
    2.
    El querystring exacto, si existe.
    Ejemplos correctos:
    /public-api/v1/sales-process/cotizaciones
    /public-api/v1/sales-process/cotizaciones/69fa7b48e65c5ec021a8aeb0
    /public-api/v1/sales-process/validaciones/imei/356789012345678?cotizacionId=69fa7b48e65c5ec021a8aeb0
    Ejemplos incorrectos:
    https://api.tu-dominio.com/public-api/v1/sales-process/cotizaciones
    http://localhost:4000/public-api/v1/sales-process/cotizaciones
    La firma no usa protocolo ni host. Solo usa pathname + search.

    4.3 Que valor va en BODY_HASH#

    BODY_HASH es el SHA-256 hexadecimal del body crudo exacto.
    Eso significa:
    1.
    Se firma el string exacto enviado por HTTP.
    2.
    Espacios, tabs, saltos de linea y orden de llaves cambian el hash.
    3.
    Si tu cliente firma un body y luego envia otro body serializado distinto, la firma falla.
    Para requests sin body, el backend hashea string vacio.
    Hash de string vacio:
    e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

    4.4 Tolerancias y protecciones del servidor#

    Por default, el backend aplica estas validaciones adicionales:
    1.
    X-Timestamp debe caer dentro de una ventana de 5 minutos.
    2.
    El nonce no puede reutilizarse.
    3.
    El TTL por default del nonce es 600 segundos.
    Si reusas un nonce, el backend responde REPLAY_DETECTED.

    5. Flujo general para firmar una request#

    1.
    Construye la URL final.
    2.
    Extrae pathname + search.
    3.
    Genera timestamp en milisegundos.
    4.
    Genera un nonce unico.
    5.
    Obtiene el body crudo exacto que vas a enviar.
    6.
    Calcula bodyHash = SHA256_HEX(rawBody).
    7.
    Construye canonical con saltos de linea.
    8.
    Calcula signature = HMAC_SHA256_HEX(HMAC_SECRET, canonical).
    9.
    Envia la request con X-Api-Key, X-Timestamp, X-Nonce y X-Signature.

    6. Reglas practicas para el body#

    6.1 GET y HEAD#

    Para GET y HEAD:
    1.
    No envias body.
    2.
    Hasheas string vacio.
    3.
    El bodyHash debe ser el hash SHA-256 de "".

    6.2 POST, PATCH, PUT y DELETE con body#

    Para requests con body:
    1.
    Usa JSON raw.
    2.
    Firma exactamente ese mismo string.
    3.
    La opcion mas estable es JSON minificado.
    Ejemplo recomendado:
    {"terminos_buro":true}
    Si firmas esto:
    {"terminos_buro":true}
    pero envias esto:
    {
      "terminos_buro": true
    }
    el hash cambia y la firma deja de ser valida.

    6.3 POST sin body#

    El servidor soporta firmar string vacio cuando no hay body.
    Sin embargo, algunos helpers de testing prefieren fallar si detectan POST sin body para evitar errores accidentales. Si usas un helper estricto y necesitas firmar un POST vacio, debes ajustar esa validacion en el helper cliente.

    7. Ejemplo reproducible completo#

    El siguiente ejemplo usa valores fijos y un secreto dummy solo para documentacion.

    7.1 Input#

    METHOD    = POST
    PATH      = /public-api/v1/sales-process/cotizaciones
    TIMESTAMP = 1778023239418
    NONCE     = 1e32736b-9bb0-4cf2-ab8d-12cdd6ef7631
    BODY      = {"terminos_buro":true}
    SECRET    = demo_hmac_secret_1234567890

    7.2 Body hash#

    9d090fbc4969d8ac1c7f2bc87a1add353990b08dbfd55710f64bb2a61d3098e3

    7.3 Canonical string#

    POST
    /public-api/v1/sales-process/cotizaciones
    1778023239418
    1e32736b-9bb0-4cf2-ab8d-12cdd6ef7631
    9d090fbc4969d8ac1c7f2bc87a1add353990b08dbfd55710f64bb2a61d3098e3

    7.4 Signature resultante#

    0fb6ebec2f82d25d3ccb6d31f07d91ef01592cfcc9d473e165c79eae14cd986b
    Este valor solo es util como caso de prueba. No uses ese secreto fuera de documentacion.

    8. Contrato de una funcion auxiliar reusable#

    La forma mas estable de integrarte es encapsular la firma en una funcion auxiliar con este contrato:
    type SignInput = {
      method: string;
      urlOrPath: string;
      body?: string | object | null;
      clientId: string;
      hmacSecret: string;
      timestamp?: string;
      nonce?: string;
    };
    
    type SignOutput = {
      path: string;
      rawBody: string;
      bodyHash: string;
      canonical: string;
      signature: string;
      headers: {
        "X-Api-Key": string;
        "X-Timestamp": string;
        "X-Nonce": string;
        "X-Signature": string;
      };
    };
    La funcion debe:
    1.
    Resolver urlOrPath a pathname + search.
    2.
    Convertir el body a string crudo una sola vez.
    3.
    Reusar ese mismo rawBody al enviar la request.
    4.
    Regresar tambien canonical y bodyHash para debug.

    9. JavaScript / Node.js#

    Este helper esta pensado para Node.js y servicios backend.

    Ejemplo con fetch#

    10. Python#

    Ejemplo con requests#

    11. PowerShell#

    function Get-Sha256Hex {
      param([string]$Value = '')
    
      $bytes = [System.Text.Encoding]::UTF8.GetBytes($Value)
      $sha = [System.Security.Cryptography.SHA256]::Create()
      try {
        $hashBytes = $sha.ComputeHash($bytes)
      }
      finally {
        $sha.Dispose()
      }
    
      -join ($hashBytes | ForEach-Object { $_.ToString('x2') })
    }
    
    function Get-HmacSha256Hex {
      param(
        [string]$Secret,
        [string]$Value
      )
    
      $keyBytes = [System.Text.Encoding]::UTF8.GetBytes($Secret)
      $valueBytes = [System.Text.Encoding]::UTF8.GetBytes($Value)
      $hmac = [System.Security.Cryptography.HMACSHA256]::new($keyBytes)
      try {
        $hashBytes = $hmac.ComputeHash($valueBytes)
      }
      finally {
        $hmac.Dispose()
      }
    
      -join ($hashBytes | ForEach-Object { $_.ToString('x2') })
    }
    
    function Convert-ToPathWithQuery {
      param([string]$UrlOrPath)
    
      if ($UrlOrPath -match '^https?://') {
        $uri = [System.Uri]$UrlOrPath
        return "$($uri.AbsolutePath)$($uri.Query)"
      }
    
      return $UrlOrPath
    }
    
    function New-PublicApiSignature {
      param(
        [string]$Method,
        [string]$UrlOrPath,
        [string]$ClientId,
        [string]$HmacSecret,
        [string]$Body = ''
      )
    
      $timestamp = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds().ToString()
      $nonce = [guid]::NewGuid().ToString()
      $path = Convert-ToPathWithQuery -UrlOrPath $UrlOrPath
      $bodyHash = Get-Sha256Hex -Value $Body
      $canonical = @($Method.ToUpper(), $path, $timestamp, $nonce, $bodyHash) -join "`n"
      $signature = Get-HmacSha256Hex -Secret $HmacSecret -Value $canonical
    
      return [pscustomobject]@{
        Path = $path
        Body = $Body
        BodyHash = $bodyHash
        Canonical = $canonical
        Signature = $signature
        Headers = @{
          'X-Api-Key' = $ClientId
          'X-Timestamp' = $timestamp
          'X-Nonce' = $nonce
          'X-Signature' = $signature
        }
      }
    }

    Ejemplo de uso#

    $BASE_URL = 'http://localhost:4000'
    $CLIENT_ID = 'pk_live_xxx'
    $HMAC_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
    $body = '{"terminos_buro":true}'
    
    $signed = New-PublicApiSignature `
      -Method 'POST' `
      -UrlOrPath '/public-api/v1/sales-process/cotizaciones' `
      -ClientId $CLIENT_ID `
      -HmacSecret $HMAC_SECRET `
      -Body $body
    
    Invoke-RestMethod `
      -Method POST `
      -Uri "$BASE_URL$($signed.Path)" `
      -Headers $signed.Headers `
      -ContentType 'application/json' `
      -Body $signed.Body

    12. C##

    13. Bash + OpenSSL#

    Este ejemplo es util para Linux o CI simples.

    14. Postman / Apidog - script auxiliar#

    Este script esta pensado para pre-request script y toma CLIENT_ID y HMAC_SECRET desde variables globales.

    Script recomendado#

    Que hace este script#

    1.
    Lee HMAC_SECRET y CLIENT_ID desde variables globales.
    2.
    Obtiene el metodo HTTP real.
    3.
    Resuelve variables de entorno dentro de la URL.
    4.
    Extrae pathname + search.
    5.
    Para metodos con body, exige body raw.
    6.
    Obtiene el body exacto despues de resolver variables.
    7.
    Calcula bodyHash con SHA-256.
    8.
    Construye el canonical string.
    9.
    Calcula la firma HMAC SHA-256.
    10.
    Inserta los headers requeridos.
    11.
    Imprime valores utiles en consola para debug.

    Nota importante sobre este helper#

    Este helper es intencionalmente estricto: si detecta un metodo con body pero el body esta vacio, lanza error.
    Eso es util para evitar requests mal firmadas en testing, pero significa que:
    1.
    Funciona muy bien para endpoints POST o PATCH que siempre llevan body.
    2.
    Si necesitas firmar un metodo sin body distinto de GET o HEAD, debes quitar la validacion que rechaza BODY vacio.
    Version flexible de esa parte:

    15. Errores comunes y como resolverlos#

    15.1 INVALID_SIGNATURE#

    Ocurre cuando falla alguno de estos puntos:
    1.
    Falta X-Timestamp, X-Nonce o X-Signature.
    2.
    X-Timestamp esta fuera de la tolerancia.
    3.
    El PATH firmado no coincide con la request real.
    4.
    El bodyHash no coincide con el body enviado.
    5.
    Se uso un HMAC_SECRET incorrecto.
    Checklist de debug:
    1.
    Verifica que firmaste pathname + search y no la URL completa.
    2.
    Verifica que el body firmado sea el mismo string que se envio.
    3.
    Verifica que no hubo reordenamiento de JSON por tu cliente.
    4.
    Verifica que el reloj del cliente este sincronizado.
    5.
    Verifica que X-Api-Key corresponda al mismo HMAC_SECRET.

    15.2 REPLAY_DETECTED#

    Ocurre cuando el nonce ya fue usado antes por la misma API key.
    Buenas practicas:
    1.
    Genera un UUID nuevo por request.
    2.
    No reutilices requests firmadas en reintentos.
    3.
    Si reintentas, genera nuevo timestamp, nuevo nonce y nueva signature.

    15.3 UNAUTHORIZED#

    Ocurre cuando:
    1.
    Falta X-Api-Key.
    2.
    La key no existe.
    3.
    La key fue revocada.

    15.4 KEY_EXPIRED#

    La API key ya expiro. Debe renovarse o rotarse segun el flujo administrativo.

    15.5 KEY_SUSPENDED#

    La API key existe pero esta suspendida. Debe reactivarse del lado administrativo.

    15.6 IP_NOT_ALLOWED#

    La IP origen no coincide con la whitelist configurada para esa key.

    15.7 RATE_LIMIT_EXCEEDED#

    La key supero su limite configurado de requests.

    16. Debug util en desarrollo#

    Cuando el servidor corre en development, una firma invalida puede devolver un bloque debug con informacion muy util, por ejemplo:
    1.
    method
    2.
    path
    3.
    timestamp
    4.
    nonce
    5.
    bodyHash
    6.
    canonical
    7.
    receivedSignature
    8.
    expectedSignature
    Esto ayuda a detectar exactamente donde difiere la firma cliente contra la del servidor.
    En ambientes no development, la API responde el error resumido sin exponer ese detalle.

    17. Recomendaciones de implementacion#

    1.
    Implementa la firma en una sola funcion auxiliar reutilizable.
    2.
    Haz que esa funcion devuelva tambien rawBody, bodyHash y canonical.
    3.
    Envia siempre el mismo rawBody que se uso para firmar.
    4.
    Usa JSON minificado en integraciones automatizadas.
    5.
    Genera un nonce nuevo por request y no lo reutilices en reintentos.
    6.
    Manten HMAC_SECRET solo del lado servidor.
    7.
    Loggea path, timestamp, nonce, bodyHash y canonical en tu cliente cuando estes integrando.
    8.
    No incluyas HMAC_SECRET en logs.

    18. Checklist rapido#

    1.
    Tienes CLIENT_ID y HMAC_SECRET correctos.
    2.
    En X-Api-Key envias CLIENT_ID.
    3.
    Firmas pathname + search, no la URL completa.
    4.
    El timestamp va en milisegundos.
    5.
    El nonce es unico.
    6.
    El body firmado es exactamente el body enviado.
    7.
    X-Signature esta en hexadecimal SHA-256 HMAC.
    8.
    Si reintentas, vuelves a firmar todo.
    Modificado en 2026-05-07 00:17:28
    Anterior
    Proceso de Venta
    Siguiente
    Cotizacion - crear
    Built with