Motify Developers Sandbox · api.motify.illari.ai

Autenticación de usuarios finales

Punto de partida: el login por WhatsApp no es el login diario — es el ALTA de un dispositivo (y la recuperación de la cuenta). El OTP inverso verifica el número (el ancla de la cuenta) y autoriza a ese equipo; acto seguido el dispositivo crea su passkey, que desde entonces es su credencial: login y renovación del token sin tocar WhatsApp. Si un dispositivo no crea passkey, puede seguir entrando por WhatsApp cada vez — soportado, pero tu app debe crear la passkey inmediatamente tras la verificación. Esta página cubre el ciclo completo (usuario, dispositivos, sesiones, renovación, revocación y recuperación) y es también su laboratorio: cada paso ejecuta llamadas reales al sandbox.

Anti fijación de sesión. Enviar el código NO verifica: el sistema responde con botones y solo el tap en "Sí, soy yo" — en el WhatsApp del dueño del número, viendo qué dispositivo intenta entrar — completa la verificación. Un link reenviado a otra persona muere en esa pantalla ("No, bloquear" además nos alerta del intento).

La secuencia

sequenceDiagram autonumber participant App as App (ciudadano) participant API as Identity API participant WA as WhatsApp del usuario App->>API: POST /verify/start { platform, model } API-->>App: { code, sessionId, waLink } App->>WA: abre waLink (mensaje pre-escrito) WA->>API: usuario envía "VUFH7C" (webhook firmado) API-->>WA: "Confirmación de ingreso · dispositivo · hora" [Sí, soy yo] [No, bloquear] WA->>API: tap "Sí, soy yo" (button_reply) API-->>WA: "Identidad verificada" Note over App,API: la app está suscrita por MQTT a motify/verify/{sessionId} API-->>App: push "VERIFIED" (al instante, sin polling) App->>API: GET /verify/status (una vez, por el JWT) API-->>App: VERIFIED + userId + accessToken (JWT 1h) App->>API: GET /v1/me (Bearer)

Laboratorio en vivo

1Inicia la verificación del usuario POST/identity/v1/auth/verify/start
Estos dos campos forman la línea “Dispositivo” del mensaje de confirmación que llega a tu WhatsApp: le dicen al dueño del número quién está intentando entrar antes de aceptar. Son contexto de seguridad para el usuario, no un control criptográfico.
crea la sesión: recibirás code (lo que enviarás por WhatsApp), sessionId (secreto de la sesión: sirve para el status y como credencial del push MQTT) y waLink (abre el chat listo)
// la respuesta real aparecerá aquí
2Envía el código y confirma con el botón GET/verify/status · push MQTT + fallback
1 · esperando tu mensaje 2 · confirma en WhatsApp: "Sí, soy yo" 3 · verificado
// ejecuta el paso 1 — la espera arranca sola (push MQTT, con polling de fallback)
3Usa tu token GET/identity/v1/me · Bearer
el mismo JWT vale para TODAS las APIs de Motify (1 h)
// tus claims aparecerán aquí

Después de la verificación: passkey — el login (y "refresh") de todos los días

WhatsApp se usa una sola vez. Con el JWT de la verificación, el dispositivo registra una passkey (WebAuthn: biometría del sistema, sin contraseñas). Desde entonces, entrar — y renovar el token cada hora — es una firma silenciosa: Motify no almacena refresh tokens que robar ni revocar.

4Enrola este dispositivo (crea su passkey) POST/passkey/register/start · finish
requiere el token del paso 2 · tu navegador pedirá biometría o PIN · el nombre del dispositivo sale de los campos del paso 1
// completa los pasos 1-2 primero
GET /passkey/credentials · los dispositivos enrolados de tu cuenta, con revocación por fila
5Entra SOLO con la passkey (así se renueva el token) POST/passkey/login/start · finish
público: sin token, sin usuario — la passkey identifica la cuenta. Este flujo ES el "refresh" (1×/hora de uso)
// firma un desafío y recibe un JWT nuevo — sin WhatsApp, sin contraseña

El modelo: usuario · dispositivo · sesión

Son tres cosas distintas con ciclos de vida distintos — y cada una tiene su flujo:

NivelQué esAltaBajaVida
Usuario La cuenta, anclada al número de teléfono Verificación por WhatsApp (pasos 1-2) — una vez por cuenta (y en cada recuperación) Solo por soporte/bloqueo Permanente
Dispositivo Cada equipo autorizado a entrar (su passkey) Enrolamiento del dispositivo (paso 4). El primero pasa con el JWT del enroll; cada dispositivo adicional exige verificación por WhatsApp fresca (≤10 min) — si no, 409 requiere_verificacion DELETE /passkey/credentials/{id} — revocarlo Hasta revocarlo
Sesión El JWT en memoria de la app Login con passkey (paso 5) Descartar el token (ver abajo) 1 hora
Cierre de sesión. Los JWT son sin estado: "cerrar sesión" = la app descarta su token (borra memoria/almacenamiento local) — no existe un endpoint de logout porque no hay nada que borrar en el servidor. Para "cerrar sesión en este dispositivo para siempre", revoca su passkey: los tokens ya emitidos mueren solos en ≤1 hora y ese equipo no puede volver a entrar. Para "cerrar sesión en todos lados", revoca todos los dispositivos — la cuenta queda intacta y se recupera re-verificando por WhatsApp. No hay lista de revocación de tokens por diseño: el authorizer de $0 no consulta estado, y la ventana máxima de exposición es 1 hora.
¿Limitar a un solo dispositivo? Existe la política (PASSKEY_MAX_DEVICES; al alcanzarla, register/start responde 409 limite_dispositivos y la app ofrece revocar uno). Matiz importante: una passkey sincronizada (iCloud/Google) vive en todos los equipos del usuario a la vez — el límite cuenta credenciales, no aparatos físicos. Hoy: sin límite.
Alta de dispositivo adicional — protección contra escalación. Un JWT robado (o un teléfono prestado 30 segundos) NO alcanza para plantar una passkey extra: si la cuenta ya tiene dispositivos, register/start exige un sello de verificación fresca (≤10 min desde una verificación por WhatsApp confirmada). El flujo de la app: recibir 409 requiere_verificacion → correr el la verificación (pasos 1-2, donde el dueño ve QUÉ dispositivo pide entrar y consiente con el botón) → reintentar el registro. Toda alta de dispositivo, la primera y las siguientes, pasa por el WhatsApp del dueño.
El mensaje de WhatsApp se adapta al contexto (con el deviceId que tu app persiste y envía en verify/start): cuenta nueva → "Bienvenido a Motify"; dispositivo habitual (deviceId ya enrolado) → "Tu dispositivo habitual está iniciando sesión"; dispositivo desconocido → "Nuevo dispositivo intenta entrar a tu cuenta" (tono de alerta). Y una cuenta dormida (sin actividad de dispositivos por más de 60 días) recibe el MISMO mensaje que una cuenta nueva — no se filtra que existía — y al confirmar arranca perfil limpio: la cuenta anterior se archiva (recuperable solo por soporte). Es la defensa contra números prepago reciclados. El deviceId es auto-reportado: afina el mensaje; la seguridad sigue siendo el tap del dueño.

El ciclo completo

flowchart LR A[Equipo nuevo] -->|"verificación WhatsApp
(alta usuario si no existe
+ consentimiento)"| B[JWT 1 h] B -->|"crear passkey
(alta de dispositivo)"| C[Dispositivo enrolado] C -->|"login passkey
(diario + renovación)"| B B -.->|"expira o descarte
(cierre de sesión)"| C C -->|"revocación
DELETE credentials/id"| D[Dispositivo fuera] D -->|"re-verificación WhatsApp
(recuperación: MISMO userId)"| B

Recuperación de cuenta = la misma verificación: teléfono nuevo o todos los dispositivos revocados → verificar el número por WhatsApp devuelve el mismo userId (el número es el ancla) y habilita enrolar el equipo nuevo. No existen cuentas huérfanas ni "olvidé mi contraseña": no hay contraseñas.

Renovación de sesión — el "refresh token" de Motify

Motify no emite refresh tokens, a propósito: un refresh token es un secreto de larga vida que hay que almacenar, rotar y poder revocar — y que se puede robar. Aquí ese rol lo cumple la passkey: cuando el JWT expira (1 hora), la app firma un desafío en silencio y recibe un token nuevo. Mismo resultado que un refresh, sin ningún secreto persistido en el servidor.

sequenceDiagram autonumber participant App participant API as Cualquier API de Motify participant ID as Identity API App->>API: GET /v1/rides/active · Bearer (token vencido) API-->>App: 401 Unauthorized App->>ID: POST /passkey/login/start ID-->>App: { challengeId, options } Note over App: navigator.credentials.get() — firma local,
sin WhatsApp ni pantallas de login App->>ID: POST /passkey/login/finish { challengeId, credential } ID-->>App: 200 { accessToken NUEVO, expiresIn 3600 } App->>API: reintenta GET /v1/rides/active · Bearer nuevo API-->>App: 200

El patrón que tu app debe implementar es un envoltorio de red con reintento único:

async function motifyFetch(url, init = {}) {
  const call = () => fetch(url, { ...init,
    headers: { ...init.headers, authorization: "Bearer " + session.token } });

  let res = await call();
  if (res.status === 401) {          // token vencido (o revocado)
    session.token = await passkeyLogin();  // login/start → firma → login/finish
    res = await call();              // reintenta UNA vez
  }
  return res;
}
Cadena de degradación: si el dispositivo ya no tiene la passkey (equipo nuevo sin sincronización), passkeyLogin() falla → la app cae a la verificación por WhatsApp, que re-vincula la misma cuenta (el número es el ancla). Nunca hay una sesión "muerta sin salida".

Puedes ver este ciclo en vivo arriba: deja vencer el token del paso 2 (1 hora) y ejecuta el paso 5 — o córrelo de inmediato: el JWT que devuelve reemplaza al anterior, que es exactamente lo que hará tu app cada hora de uso.

Reglas que tu app debe respetar

ReglaDetalle
Estados del pollingPENDING (espera el envío) → AWAITING_CONFIRM (muestra "confirma en tu WhatsApp") → VERIFIED (token en la misma respuesta) · DENIED (403: aborta y no reintentes solo).
SesiónExpira a los 10 min → 404. Reinicia con verify/start.
Espera del estadoSuscríbete por MQTT a motify/verify/{sessionId} (credencial: verify.<sessionId> en el authorizer) — los estados llegan por push al instante. Mantén un polling de fallback cada 10-15 s por si la conexión falla; nunca más rápido que 2 s.
Vida del token1 hora. Ante 401 en cualquier API: re-autentica y reintenta UNA vez.
IdentidaduserId es estable por número: re-verificar devuelve el MISMO userId. Guarda el userId, no el teléfono.
Dispositivoplatform/model son contexto UX del mensaje de confirmación — envíalos siempre (mejora la decisión del usuario), pero no son un control de seguridad.
Renovación del tokenSin refresh tokens: ante 401, repite passkey/login en silencio (una firma) y reintenta. Solo si el dispositivo perdió su passkey se vuelve a la verificación por WhatsApp.
PasskeyRegístrala INMEDIATAMENTE tras la verificación (paso 4), de forma transparente para el usuario. RP ID: motify.illari.ai.
Dispositivo adicionalregister/start409 requiere_verificacion: corre la verificación por WhatsApp de nuevo y reintenta en ≤10 min. 409 limite_dispositivos: ofrece revocar uno (lista + DELETE).
RecuperaciónTeléfono/equipo nuevo = misma verificación por WhatsApp → mismo userId. Tras recuperar, ofrece al usuario revisar y revocar sus dispositivos viejos.