docs
Guías de Integración

Autenticación

Descripción General

La API de FyPay utiliza autenticación basada en OAuth 2.0 (clientId + clientSecret) combinada con un certificado de cliente X.509 (mTLS). El certificado se presenta en el handshake TLS de la llamada de generación de token; las demás rutas usan solo el token Bearer.

¿Por qué mTLS?

El mTLS (mutual TLS) ofrece mayor seguridad que las credenciales simples:

  • Autenticación mutua: Tanto el cliente como el servidor se autentican en el handshake TLS
  • No repudio: Los certificados vinculados a la cuenta garantizan trazabilidad
  • Protección contra robo de credenciales: Aunque se filtren clientId/clientSecret, el atacante necesitaría el certificado y su clave privada

Requisitos Previos

Antes de comenzar, necesitará:

Certificado en formato PEM, clave RSA 2048, válido por 2 años y vinculado a su cuenta, junto con su clave privada. En los ejemplos siguientes, los archivos se llaman client.crt (certificado) y client.key (clave privada).

Genere su clientId y clientSecret en el Dashboard de FyPay, en Configuración → Credenciales. El clientSecret se muestra una sola vez: cópielo y guárdelo en un almacén de secretos.

Configure su cliente HTTP para presentar el certificado y la clave privada en el handshake TLS (mecanismo nativo de TLS de su lenguaje).

El certificado debe estar vinculado a su cuenta. Los certificados no vinculados, o vinculados a otra cuenta, se rechazan aunque sean técnicamente válidos.

El certificado se entrega solo en PEM; no existe archivo .pfx. Si su stack requiere PKCS#12 (por ejemplo, algunos escenarios Windows/.NET), conviértalo localmente:

openssl pkcs12 -export -in client.crt -inkey client.key -out client.pfx

Endpoint de Autenticación

POST /api/auth/token

Genera un token JWT de acceso. El tiempo de vida del token, en segundos, viene en el campo expires_in de la respuesta.

Esta es la única ruta que exige el certificado de cliente. Debe presentarse en el handshake TLS (mTLS), no en un encabezado HTTP.

Request

curl -X POST https://api.fypay.me/api/auth/token \
  --cert client.crt \
  --key client.key \
  -H "Content-Type: application/json" \
  -d '{
    "clientId": "6967c0a0-3566-48d0-8c13-717038b52279",
    "clientSecret": "su-client-secret"
  }'
CampoTipoObligatorioDescripción
clientIdstring (UUID v4)SíIdentificador de la credencial generada en el dashboard
clientSecretstringSíSecreto de la credencial, mostrado una sola vez en el dashboard

Response (201 Created)

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 600,
  "scope": "email profile"
}
CampoDescripción
access_tokenToken JWT que se envía en Authorization: Bearer <token>
token_typeSiempre Bearer
expires_inTiempo de vida del token en segundos. Use este valor para programar la renovación
scopeAlcance del token

Ejemplo Práctico: Node.js

Instalación

npm install undici

Código Completo

const fs = require('fs');
const { Agent, request } = require('undici');

const mtlsAgent = new Agent({
  connect: {
    cert: fs.readFileSync('./client.crt'),
    key: fs.readFileSync('./client.key'),
  },
});

async function getToken() {
  const { statusCode, body } = await request('https://api.fypay.me/api/auth/token', {
    method: 'POST',
    dispatcher: mtlsAgent,
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      clientId: process.env.FYPAY_CLIENT_ID,
      clientSecret: process.env.FYPAY_CLIENT_SECRET,
    }),
  });

  const data = await body.json();

  if (statusCode !== 201) {
    throw new Error(`Error al obtener el token: ${data.code} ${data.message}`);
  }

  console.log('¡Token obtenido con éxito!');
  console.log('Expira en:', data.expires_in, 'segundos');

  return data;
}

getToken().catch(console.error);

Con el módulo nativo https, el equivalente es new https.Agent({ cert, key }) pasado en la opción agent de la solicitud.

Ejemplo Práctico: Python

Instalación

pip install requests

Código Completo

import os
import requests

url = 'https://api.fypay.me/api/auth/token'
payload = {
    'clientId': os.environ.get('FYPAY_CLIENT_ID'),
    'clientSecret': os.environ.get('FYPAY_CLIENT_SECRET'),
}

try:
    response = requests.post(
        url,
        json=payload,
        cert=('client.crt', 'client.key'),
        timeout=30,
    )
    response.raise_for_status()

    data = response.json()

    print('¡Token obtenido con éxito!')
    print(f"Expira en: {data['expires_in']} segundos")

except requests.exceptions.RequestException as e:
    print(f'Error al obtener el token: {e}')
    if e.response is not None:
        print(f'Respuesta: {e.response.text}')

Otros lenguajes

En todos ellos, el certificado y la clave se cargan mediante el mecanismo nativo de TLS del cliente HTTP:

LenguajeMecanismo
PHP (cURL)CURLOPT_SSLCERT = client.crt y CURLOPT_SSLKEY = client.key
C# (.NET)HttpClientHandler.ClientCertificates.Add(X509Certificate2.CreateFromPemFile("client.crt", "client.key"))
Gotls.LoadX509KeyPair("client.crt", "client.key") en tls.Config.Certificates
JavaConvierta a PKCS#12 con openssl pkcs12 -export y cárguelo como KeyStore en el SSLContext

Uso del Token

Después de obtener el token, inclúyalo en el encabezado Authorization de las demás solicitudes. Estas rutas no exigen certificado de cliente:

curl -X GET https://api.fypay.me/api/balance \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Renovación del Token

No asuma una duración fija para el token. Lea expires_in en cada respuesta y renueve el token antes de que expire.

Estrategia Recomendada

const fs = require('fs');
const { Agent, request } = require('undici');

class TokenManager {
  constructor(clientId, clientSecret, certPath, keyPath) {
    this.clientId = clientId;
    this.clientSecret = clientSecret;
    this.agent = new Agent({
      connect: {
        cert: fs.readFileSync(certPath),
        key: fs.readFileSync(keyPath),
      },
    });
    this.token = null;
    this.expiresAt = 0;
  }

  async getValidToken() {
    const safetyMarginMs = 30000;
    if (this.token && Date.now() < this.expiresAt - safetyMarginMs) {
      return this.token;
    }
    return this.refreshToken();
  }

  async refreshToken() {
    const { statusCode, body } = await request('https://api.fypay.me/api/auth/token', {
      method: 'POST',
      dispatcher: this.agent,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        clientId: this.clientId,
        clientSecret: this.clientSecret,
      }),
    });

    const data = await body.json();
    if (statusCode !== 201) {
      throw new Error(`${data.code}: ${data.message}`);
    }

    this.token = data.access_token;
    this.expiresAt = Date.now() + data.expires_in * 1000;
    return this.token;
  }
}

const tokenManager = new TokenManager(
  process.env.FYPAY_CLIENT_ID,
  process.env.FYPAY_CLIENT_SECRET,
  './client.crt',
  './client.key'
);

const token = await tokenManager.getValidToken();

Validación del Certificado

El sistema realiza las siguientes validaciones sobre el certificado presentado en el handshake:

  1. Handshake mTLS: El certificado debe haber sido emitido por FyPay y usted debe poseer la clave privada correspondiente
  2. Vigencia: El certificado debe estar dentro de su período de validez (2 años desde la emisión)
  3. Registro: El fingerprint SHA-256 del certificado debe estar registrado y activo
  4. Vinculación a la cuenta: El certificado debe estar vinculado a la misma cuenta de la credencial (clientId)

Los certificados no vinculados o vinculados a otra cuenta serán rechazados, aunque sean técnicamente válidos.

Respuestas de error

Todos los errores devueltos por POST /api/auth/token siguen el mismo formato:

{
  "statusCode": 400,
  "timestamp": "2026-04-24T16:59:25.577Z",
  "path": "/api/auth/token",
  "method": "POST",
  "code": "PUB_CERT_HEADER_MISSING",
  "message": "Client certificate header is missing",
  "userMessage": "Client certificate is required.",
  "details": {
    "hint": "Present your client certificate during the TLS handshake (e.g. curl --cert client.crt --key client.key)."
  },
  "errorId": "81421e7a5afbd9cf25212f51dac08da1"
}
  • code: identificador estable legible por máquina. Base su tratamiento en este campo.
  • message: descripción técnica breve (en inglés, orientada a logs).
  • userMessage: descripción orientada al usuario final.
  • details.hint: orientación práctica para desarrolladores que integran la API.
  • errorId: identificador único de la solicitud. Infórmelo al contactar al soporte.

Referencia de códigos de error

HTTPCódigoSignificado
400PUB_CERT_HEADER_MISSINGNo se presentó ningún certificado de cliente en el handshake TLS
400PUB_CERT_MALFORMED_PEMCertificado en formato PEM inválido
400PUB_REQUEST_BODY_INVALIDCuerpo JSON inválido
401PUB_CERT_NOT_VERIFIEDEl certificado presentado en el handshake fue rechazado
401PUB_CERT_NOT_YET_VALIDLa fecha de inicio de validez del certificado está en el futuro
401PUB_CERT_EXPIREDEl certificado expiró
401PUB_CERT_NOT_REGISTEREDEl certificado no está registrado o fue revocado
401PUB_INVALID_CREDENTIALSclientId o clientSecret incorrectos
403PUB_CERT_NOT_AUTHORIZED_FOR_ACCOUNTEl certificado no está vinculado a la cuenta del clientId
403PUB_ACCOUNT_INACTIVECredenciales correctas, pero la cuenta no está activa
502PUB_AUTH_UPSTREAM_ERRORError inesperado en el servicio de autenticación
503PUB_AUTH_UPSTREAM_UNAVAILABLEServicio de autenticación temporalmente no disponible

PUB_CERT_HEADER_MISSING (400)

No se presentó ningún certificado de cliente en el handshake TLS.

Cómo resolver: configure su cliente HTTP para enviar el certificado y la clave privada en la conexión TLS (por ejemplo, curl --cert client.crt --key client.key). Si hay un proxy o gateway entre su aplicación y FyPay que termine el TLS, la conexión con api.fypay.me debe partir de un cliente que presente el certificado.

PUB_CERT_MALFORMED_PEM (400)

Certificado en formato PEM inválido.

PUB_REQUEST_BODY_INVALID (400)

El cuerpo JSON no cumplió el esquema (campo ausente, tipo incorrecto, o clientId no es un UUID v4). Revise details.violations para identificar los campos con problema y envíe Content-Type: application/json.

PUB_CERT_NOT_VERIFIED (401)

El certificado presentado en el handshake TLS fue rechazado.

Cómo resolver: use el certificado emitido por FyPay para su cuenta y la clave privada correspondiente. No se aceptan certificados de otras autoridades certificadoras.

PUB_CERT_NOT_YET_VALID (401)

La fecha notBefore del certificado está en el futuro.

Cómo resolver: sincronice el reloj del sistema mediante NTP. Si la fecha realmente está en el futuro, espere o solicite un nuevo certificado.

PUB_CERT_EXPIRED (401)

La fecha notAfter del certificado ya pasó. Los certificados son válidos por 2 años desde su emisión.

Cómo resolver: solicite un nuevo certificado al soporte y reemplácelo en su integración. Planifique el cambio antes del vencimiento.

PUB_CERT_NOT_REGISTERED (401)

El fingerprint SHA-256 del certificado no está en el registro: nunca fue registrado o fue revocado.

Cómo resolver: calcule el fingerprint local con openssl x509 -noout -fingerprint -sha256 -in client.crt e infórmelo al soporte junto con el errorId de la respuesta.

PUB_INVALID_CREDENTIALS (401)

El clientId no es reconocido, o el clientSecret no coincide. La plataforma deliberadamente no diferencia estos casos (resistencia a enumeración).

Cómo resolver: verifique el clientId y el clientSecret. Como el clientSecret solo se muestra una vez, si lo perdió genere una nueva credencial en Configuración → Credenciales.

PUB_CERT_NOT_AUTHORIZED_FOR_ACCOUNT (403)

El certificado es válido y está registrado, pero no está vinculado a la cuenta propietaria del clientId informado.

Cómo resolver: verifique que está usando el par certificado/clientId de la misma cuenta.

PUB_ACCOUNT_INACTIVE (403)

Las credenciales son correctas, pero la cuenta no está activa. Los reintentos no tendrán éxito.

Cómo resolver: contacte al soporte con el errorId de la respuesta.

PUB_AUTH_UPSTREAM_UNAVAILABLE (503)

El servicio de autenticación está temporalmente inaccesible.

Cómo resolver: reintente con backoff exponencial (por ejemplo, 1s, 2s, 4s). Si el problema persiste por más de un minuto, contacte al soporte.

PUB_AUTH_UPSTREAM_ERROR (502)

El servicio de autenticación devolvió un error inesperado.

Cómo resolver: contacte al soporte con el errorId.

Solución de problemas

"Recibo PUB_CERT_HEADER_MISSING aunque envío el certificado"

El certificado debe ir en el handshake TLS, no en un encabezado HTTP. Confirme que el cliente HTTP está configurado con certificado y clave (por ejemplo, --cert/--key en curl, cert=(crt, key) en Python requests) y que ningún proxy intermedio termina el TLS antes de llegar a FyPay.

"Recibo ocasionalmente PUB_CERT_NOT_YET_VALID"

El reloj del sistema está atrasado respecto a la fecha notBefore del certificado. Configure NTP y mantenga el reloj del host a pocos segundos de UTC. Los contenedores pueden heredar la deriva del reloj del host; verifique con date -u dentro del contenedor.

"Recibo PUB_CERT_NOT_REGISTERED después de cambiar el certificado"

Calcule el fingerprint del nuevo certificado (openssl x509 -noout -fingerprint -sha256 -in client.crt) y confirme con el soporte que está registrado y vinculado a su cuenta antes de hacer la transición.

Buenas Prácticas

Próximos Pasos

En esta página