Ir al contenido

Vincular un número

Una instancia es un número de WhatsApp conectado al motor como un dispositivo vinculado, igual que WhatsApp Web. Esta página amplía los pasos 2 y 3 del Inicio rápido: las variantes de la vinculación, sus estados, sus eventos y qué hacer cuando algo sale mal.

POST /v1/instances (scope instances:create) con un nombre (de 1 a 120 caracteres) y, si quieres, un owner_ref: una referencia opaca tuya de hasta 200 caracteres (la sucursal, el vendedor). El motor no la interpreta; te la devuelve en cada lectura.

Respuesta Cuándo
201 Instancia nueva, en estado nuevo y con generacion: 0.
200 Repetiste el pedido con la misma Idempotency-Key y los mismos datos: es la misma instancia.
403 La key está restringida a instancias: no puede crear.
409 La misma Idempotency-Key con otros datos.
429 La app llegó a su cupo de instancias (3 de fábrica). No es temporal: no trae Retry-After.

La Idempotency-Key es opcional aquí, pero úsala: si la red se corta durante el alta, repetirla no crea una segunda instancia ni gasta cupo. Las instancias archivadas no cuentan para el cupo.

Modo Cómo se usa Cuándo conviene
codigo El motor pide a WhatsApp un código de 8 caracteres para el número que indicas. En el teléfono: Dispositivos vinculados → Vincular un dispositivo → Vincular con el número de teléfono. Cuando quien vincula no puede escanear una pantalla (vincula desde el mismo teléfono, o a distancia).
qr El motor genera un QR; se escanea desde Dispositivos vinculados. Cuando la persona tiene delante la pantalla de tu app.
passkey No se elige: WhatsApp la puede exigir durante un intento con QR o código. Ver La passkey.

POST /v1/instances/{id}/pairing (scope instances:pair) con modo, telefono (solo en modo codigo) y, opcional, generacion_esperada. Responde 202 en cuanto el intento quedó guardado: el reto llega unos segundos después.

202 Accepted
{
"instancia_id": "8a1d2c3e-0000-4000-8000-0000000000a1",
"generacion": 1,
"modo": "qr",
"fase": "iniciando",
"estado": "esperando_qr"
}

Lee el reto con GET /v1/instances/{id}/pairing (mismo scope) hasta que traiga reto, y después sigue leyendo la instancia hasta que llegue a conectado:

Ventana de terminal
curl -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/pairing \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"modo": "qr"}'
# Repite hasta que reto no sea null (y antes de reto.expira_en):
curl https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/pairing \
-H "Authorization: Bearer TU_API_KEY"
GET …/pairing · 200 OK
{
"generacion": 1,
"modo": "qr",
"fase": "esperando_qr",
"reto": { "tipo": "qr", "valor": "…", "expira_en": "2026-10-24T15:03:05Z" },
"creado_en": "2026-10-24T15:02:05Z",
"terminado_en": null
}
  • reto.valor es el texto del QR, no una imagen: dibújalo con tu biblioteca de QR. En modo codigo, es el código de 8 caracteres.
  • El QR se renueva. WhatsApp entrega varios QR seguidos (el primero dura 60 s; después, cinco más de 20 s): reto.valor y reto.expira_en cambian. Vuelve a leer el reto y redibújalo, como hace el ejemplo. El evento pairing.available solo avisa del primero.
  • El intento vive unos 160 segundos en los dos modos. En la prueba A12 (16/10/2026), un código que no se usó venció a los 2 min 40 s.
  • reto es null mientras no hay uno vigente, y también desde que el teléfono escaneó.
  • Antes del primer intento, GET …/pairing responde 404.
fase Qué significa
iniciando El motor está abriendo la conexión de login.
esperando_qr Hay un QR vigente.
esperando_codigo Hay un código vigente: falta escribirlo en el teléfono.
accion_requerida WhatsApp pidió una passkey.
emparejando El teléfono aceptó; el motor está registrando el dispositivo.
completado Vinculado.
vencido Se acabó el tiempo sin vincular.
fallido WhatsApp o el motor rechazaron el intento: error dice por qué.
reemplazado Pediste otro intento antes de terminar este.

Cada POST …/pairing abre una generación nueva (generacion sube de uno en uno) y cierra la anterior como reemplazado, sin desvincular nada. Por eso un QR vencido se resuelve pidiendo otro. La generación sirve para tres cosas:

  • Retos viejos. Un reto o un aviso de una generación anterior se descarta: nunca se muestra.
  • Comandos viejos. Un mensaje aceptado antes de volver a vincular no sale por el número nuevo: termina fallido con generacion_cambiada.
  • Carreras entre pantallas. Manda generacion_esperada con la generación que tu app está mostrando. Si otra persona pidió otro intento mientras tanto, la respuesta es 409 («La generación no coincide») en vez de reemplazar su intento.

Cada evento trae la generation de la instancia en el sobre: un evento de una generación vieja queda en el ledger, pero no cambia el estado.

estado es lo que el motor observa; estado_deseado (activo, pausado, desvinculado o archivado) es lo que pidieron. motivo trae un código tipificado (nunca un texto del proveedor).

estado Qué significa Qué hacer
nuevo Creada, nunca vinculada. Pide un reto.
esperando_qr, esperando_codigo Hay un intento con su reto. Muéstralo.
accion_requerida WhatsApp pidió una passkey. Ver La passkey.
emparejando El teléfono aceptó; se registra el dispositivo. Esperar unos segundos.
emparejado Vinculada, todavía sin autenticar la sesión. Esperar conectado.
conectado Lista: numero trae el teléfono en formato +E.164. —
desconectado Sin conexión. motivo dice por qué (sin_conexion, pausada, apagado…). Si no la pausaste, el motor reintenta solo.
reconectando El motor está volviendo a conectar. Esperar.
intento_vencido El reto venció sin usarse (qr_vencido o codigo_vencido). Pedir otro reto.
error El intento falló (motivo) o WhatsApp rechazó la conexión. Ver Recuperarse.
cerrado_por_telefono Alguien cerró la sesión desde el teléfono. Volver a vincular.
cerrado_por_operador Se desvinculó desde el motor. Volver a vincular, si hace falta.
reemplazado Otra conexión tomó la sesión de este dispositivo. Reconectar a pedido.
bloqueado WhatsApp la bloqueó por un tiempo; bloqueada_hasta dice hasta cuándo, si lo informó. Esperar; no envía mientras tanto.
archivado Archivada: sin sesión, fuera del cupo. Su ledger se conserva. Es definitivo: crea otra instancia.

Un vínculo con código que entró a la primera, en la prueba A1 (15/10/2026): nuevo → esperando_codigo → emparejando → emparejado → conectado, en unos 4 segundos desde que se escribió el código.

Todo queda en el ledger de la instancia y llega por tus webhooks o streams:

Evento Cuándo data
state.changed En cada cambio de estado. de, a, motivo, generacion.
pairing.available Llega el primer reto de cada tipo de una generación. Sin el reto: léelo con GET …/pairing. modo, reto (el tipo), expira_en.
pairing.expired El intento venció. motivo: qr_vencido o codigo_vencido.
pairing.failed El intento falló. motivo (abajo).
paired El teléfono aceptó y el dispositivo quedó registrado. numero y plataforma.
connected La sesión se autenticó. —
motivo de pairing.failed Qué hacer
telefono_rechazado WhatsApp rechazó el número: revisa que esté completo, con el código de país.
sin_multidispositivo El teléfono no tiene los dispositivos vinculados activos.
cliente_desactualizado WhatsApp pide una versión más nueva del motor: hay que actualizarlo.
conexion El motor no pudo conectar con WhatsApp: revisa su salida a internet o su proxy.
error_emparejamiento, estado_inesperado, vinculo_rechazado Pide otro intento.
passkey La ceremonia de la passkey no se completó.
credencial_revocada, credencial_vencida, sin_scope, sin_acceso_a_la_instancia, app_suspendida La key que pidió el intento dejó de valer antes de terminar: pide otro con una key vigente.

El motor revalida la key que pidió el intento justo antes de registrar el dispositivo: si la revocaron mientras tanto, el vínculo se rechaza.

Si WhatsApp exige una passkey, el intento pasa a accion_requerida y el reto cambia de tipo:

  1. reto.tipo: "passkey": reto.valor trae las opciones WebAuthn en JSON. Tu app tiene que resolverlas con un autenticador válido para el origen de WhatsApp y mandar el resultado con POST /v1/instances/{id}/pairing/passkey y {"generacion": N, "respuesta": {…}} (el PublicKeyCredential serializado).
  2. reto.tipo: "passkey_confirmacion": reto.valor es un código para comparar con el que muestra el teléfono. Si coinciden, confirma con POST …/pairing/passkey/confirm y {"generacion": N}.

Si WhatsApp rechaza la respuesta, el motor responde 502 sin el detalle del servidor. El panel no puede completar esta ceremonia (el autenticador es de WhatsApp) y lo dice.

Las acciones sobre una instancia vinculada son comandos de control: POST /v1/instances/{id}/commands con {"type": "…"}. Responden 202 con el comando (aceptado); el resultado (completado o fallido) se lee en GET /v1/commands/{id} y en el estado de la instancia.

type Scope Qué hace
instance.reconnect instances:manage Cierra y vuelve a abrir el socket.
instance.disconnect instances:manage Lo cierra hasta que pidas reconectar (o hasta que el motor reinicie).
instance.pause instances:manage Lo cierra y la deja pausada, también después de reiniciar el motor.
instance.resume instances:manage La vuelve a activar.
instance.logout instances:logout Desvincula el número: avisa a WhatsApp si la sesión está autenticada.
instance.archive instances:logout Desvincula y archiva. Es lo mismo que DELETE /v1/instances/{id}.
Ventana de terminal
# Reconectar a pedido (por ejemplo, después de «reemplazado»):
curl -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/commands \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: reconectar-sucursal-centro-1" \
-d '{"type": "instance.reconnect"}'
# Volver a vincular después de un cierre desde el teléfono:
curl -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/pairing \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"modo": "codigo", "telefono": "+TU_NUMERO_CON_CODIGO_DE_PAIS"}'
Situación Qué pasó Qué hacer
intento_vencido Nadie usó el reto a tiempo. Pide otro reto: abre otra generación.
error tras un intento pairing.failed con su motivo. Corrige la causa (tabla de motivos) y pide otro reto.
desconectado o reconectando Se cortó la red o el socket. Nada: el motor reconecta solo (si config.reconexion_automatica no está apagada). Para forzarlo, instance.reconnect.
cerrado_por_telefono Cerraron la sesión desde Dispositivos vinculados. Vuelve a vincular con POST …/pairing; no hace falta reiniciar el motor.
reemplazado Otra conexión tomó la sesión. El motor no reconecta solo, ni al reiniciar. instance.reconnect cuando sepas que la otra conexión ya no corre.
bloqueado Un bloqueo temporal de WhatsApp. Esperar a bloqueada_hasta. Los envíos responden 409 mientras tanto.
Cambiar el número La instancia ya está vinculada: un reto nuevo da 409 («ya está vinculada»). instance.logout y después un reto nuevo.

En la prueba A3 (15/10/2026), después de cerrar la sesión desde el teléfono, la instancia volvió a conectado con un QR nuevo en unos 6 segundos desde el escaneo, sin reiniciar el motor. Al desvincular desde el motor (A4), el teléfono mostró la sesión como «Cierre de sesión pendiente» y la quitó en menos de un minuto.

Volver a vincular el mismo número conserva lo que la instancia ya sabía: en la prueba del 21/10/2026 (falla F23), un iPhone desvinculado y vuelto a vincular con código quedó conectado en la generación 2, con sus chats, sus contactos y sus mensajes anteriores, sin duplicados.

Respuesta Causa Qué hacer
404 «No existe esa instancia» Es de otra app, está fuera del alcance de la key o el ID está mal. Revisa whoami.
409 «La instancia ya está vinculada» Pediste un reto con un número conectado. Desvincula antes, si de verdad quieres cambiarlo.
409 «La instancia está archivada» Archivar es definitivo. Crea otra instancia.
409 «La generación no coincide» generacion_esperada vieja: alguien pidió otro intento. Vuelve a leer el emparejamiento.
422 body.telefono El número no tiene + y un código de país válido. Escríbelo en formato internacional.
422 body.modo El modo no es qr ni codigo. —
503 El transporte de WhatsApp no está disponible, o este proceso del motor está en espera. Reintenta en unos segundos; si sigue, avisa al operador.
  • El cupo de instancias es por app: 3 de fábrica, configurable en Ajustes o por app. Las archivadas no cuentan.
  • Una key restringida no crea instancias.
  • Un dispositivo, una instancia. El motor no deja que el mismo dispositivo vinculado quede en dos instancias. El mismo número sí puede vincularse en dos instancias, como dos dispositivos.
  • Lo que cambia según el teléfono y la cuenta está en Limitaciones de WhatsApp.

Con la instancia en conectado, manda el primer mensaje.