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.
| Superficie | Cómo se autentica | Quié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 Authorization | el navegador del pagador vía winal.js |
/health, /status, /pay/*, /webhooks/in/* | sin autenticación | monitoreo, checkout hosteado, callbacks del proveedor |
GET /v1/payment_intents/5b6b8b3e-... HTTP/1.1
Host: api.winal.com.mx
Authorization: Bearer sk_test_9fZ3kQ...
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.
Rota
El portal emite sk_… nueva y fija old_expires_at (fin de la gracia) a la anterior.
Despliega
Actualizas el secreto en tu backend. Ambas llaves autentican durante la gracia.
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.
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.
| Scope | Exigido por |
|---|---|
payouts:write | POST /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:manage | POST/DELETE /v1/webhook_endpoints — alta/baja de a dónde se entregan tus eventos. |
reports:write | POST /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
- TLS obligatorio. Todo el tráfico entra por HTTPS en el borde (Caddy termina TLS con certificados gestionados). Las peticiones en claro se redirigen/rechazan.
- IP de cliente confiable. Winal toma la IP real del último salto que anexa el proxy de confianza, no un
X-Forwarded-Forarbitrario — así el particionado de rate limit y la telemetría no son falsificables desde el cliente. - Sin custodia de fondos. Winal orquesta el cobro pero nunca custodia tu dinero: las CLABEs y credenciales de proveedor son tuyas (ADR-0001). Reduce drásticamente la superficie de un incidente.
- Nunca tocamos el PAN. Ningún endpoint acepta el número de tarjeta; la tokenización es del lado del cliente con los campos seguros del proveedor (PCI SAQ A). Ver Métodos de pago.
/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:
- 2FA (TOTP). Cada usuario puede activar un segundo factor con cualquier app de códigos (Google Authenticator, 1Password, Authy). Con 2FA activo, el login exige el código de 6 dígitos además de la contraseña.
- SSO / OIDC. El portal acepta inicio de sesión con el
id_tokende tu proveedor de identidad (OpenID Connect), para que tu equipo entre con las credenciales corporativas. - Roles. Distingue quién puede ver contra quién puede emitir/revocar llaves y mover configuración sensible.
- Cierre de sesiones. Puedes cerrar todas las sesiones activas de un usuario (revocación global) si sospechas de un acceso indebido.
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.