Autenticación y seguridad

GUÍA

Toda petición autenticada a Winal viaja por TLS y presenta tu llave secreta como un Bearer token. Esta página consolida en un solo lugar cómo se autentica cada superficie del API, cómo se rotan y expiran las llaves, y la postura de red y de acceso al portal.

Autenticación de la API

Winal expone tres superficies con esquemas de autenticación distintos. El securityScheme del contrato OpenAPI declara exactamente el de /v1.

SuperficieCómo se autenticaQuién la llama
/v1/*Authorization: Bearer sk_test_… / sk_live_…tu backend (nunca el navegador)
/public/*el client_secret del intent (en el cuerpo o el query); sin cabecera Authorizationel navegador del pagador vía winal.js
/health, /status, /pay/*, /webhooks/in/*sin autenticaciónmonitoreo, checkout hosteado, callbacks del proveedor
http
GET /v1/payment_intents/5b6b8b3e-... HTTP/1.1
Host: api.winal.com.mx
Authorization: Bearer sk_test_9fZ3kQ...
🔑 La llave secreta es secreta
sk_ = secret key. Va solo en tu servidor. No hay una "llave publicable" que exponer al navegador: el frente se autentica con el client_secret efímero de un intent, que solo puede confirmar ese cobro y nada más. Si una sk_ se filtra, revócala de inmediato (abajo).

Una petición a /v1 sin Authorization, con una llave mal formada o revocada, responde 401 con el envelope de error estándar (error.type = "authentication_error"; ver Errores).

Ciclo de vida de las llaves

Administras tus llaves desde portal → API Keys. Cada llave se almacena hasheada (jamás en claro): el valor completo se muestra una única vez, al crearla o rotarla. Después solo verás un prefijo enmascarado para identificarla.

Rotación sin downtime

Rotar no corta el servicio: al rotar, Winal emite una llave nueva y mantiene la anterior válida durante un periodo de gracia configurable. Despliegas la nueva, verificas que todo tu tráfico ya la usa, y entonces cierras la vieja (o dejas que expire sola al terminar la gracia). Este solape es la forma correcta de cambiar una credencial en producción.

1

Rota

El portal emite sk_… nueva y fija old_expires_at (fin de la gracia) a la anterior.

2

Despliega

Actualizas el secreto en tu backend. Ambas llaves autentican durante la gracia.

3

Cierra

Completas la rotación (o esperas a old_expires_at): la vieja deja de servir.

Expiración

Una llave puede tener expires_at. En cuanto pasa esa fecha —o si la revocas— deja de autenticar y toda petición con ella recibe 401. En el listado del portal, el campo active refleja no revocada Y (sin expiración O aún no expirada).

Revocación inmediata

Si una llave se compromete, revócala desde el portal: el corte es inmediato, sin gracia. Emite una llave nueva y actualiza tu backend. La revocación queda en la bitácora de auditoría del tenant (api_key.rotated / api_key.created y la revocación), con actor y fecha.

⚠️ Nunca en el cliente ni en el repo
Mantén sk_live_ fuera de HTML, apps móviles, repos y logs. Si tu stack lo permite, inyéctala como variable de entorno o desde un gestor de secretos. Un secreto en un commit se considera comprometido aunque borres el commit después — rótalo.

Scopes de la API key

Además de identificar al tenant, una API key lleva una lista de scopes: permisos granulares que acotan QUÉ puede hacer esa llave, más allá de a quién pertenece. Toda llave creada desde el portal admin hoy recibe el comodín * (acceso total: pasa cualquier scope), pero el modelo está pensado para llaves de alcance acotado — p. ej. una llave de integración de solo lectura, o una que jamás debería poder ordenar una dispersión de fondos.

ScopeExigido por
payouts:writePOST /v1/payouts, POST /v1/connect/transfers (y /redisperse, /release), POST /v1/payroll/runs/{id}/execute — toda operación que ORDENA una salida real de dinero por SPEI.
webhooks:managePOST/DELETE /v1/webhook_endpoints — alta/baja de a dónde se entregan tus eventos.
reports:writePOST /v1/reports/periods/{year}/{month}/close — cierre irreversible de un período contable.
*Comodín de acceso total: satisface cualquier scope que un endpoint exija. Es lo que trae toda llave emitida hoy desde el portal.

Una llave sin el scope requerido recibe 403 con error.type = "authorization_error" y error.code = "insufficient_scope" (ver Errores) — el mensaje nombra exactamente el scope que falta. Es aditivo: un endpoint sin scope declarado no cambia de comportamiento, y las operaciones de solo lectura (listar/consultar) nunca lo exigen.

Idempotencia como salvaguarda

Todo POST que mueve dinero exige un header Idempotency-Key (un UUID que tú generas). Reintentar con la misma clave devuelve la respuesta original sin duplicar el cargo — tu red de seguridad ante timeouts y reintentos. Es una de las tres capas de idempotencia de Winal (API, hacia el proveedor, y dedupe por event_id en tus consumidores de webhook). Detalle en Referencia de API.

Límite de solicitudes

El API aplica rate limiting por ventana fija de 1 minuto. Las peticiones autenticadas se cuentan por llave; las de /public/* por IP de cliente. Al excederlo recibes 429 con Retry-After: 60. Los detalles y los límites por defecto están en Referencia → Límites de solicitudes.

Postura de red

mTLS y allowlist de IP por llave
Hoy la autenticación de /v1 es Bearer sobre TLS. El allowlist de IP por llave y el mTLS mutuo para clientes de plataforma están en el roadmap de endurecimiento y se habilitan por acuerdo (no son configurables self-service todavía). Si tu caso los requiere para cumplimiento, indícalo en el alta.

Acceso al portal

El portal de configuración es multi-usuario, con roles por usuario y sesiones por cookie. Protégelo con:

Las llaves de API y el acceso al portal son planos de seguridad separados: cerrar la sesión de un usuario no invalida las sk_, y revocar una sk_ no cierra sesiones del portal. Gestiona cada uno según su riesgo.