Números
Un número (un «canal») es una línea de WhatsApp conectada a tu cuenta. Conectar una es un flujo fail-closed: declaras el número, renderizas el QR que devolvemos y lo escaneas desde ese teléfono, y Sendveo lee el número emparejado antes de marcarlo como conectado. El QR es tuyo para mostrarlo: no hay página externa ni redirección.
El objeto número
{
"id": "chn_sales01",
"displayName": "Sales line",
"declaredNumber": "+212661234567",
"pairedNumber": "+212661234567",
"status": "CONNECTED",
"connectedAt": "2026-08-19T09:12:00Z",
"lastReconnectAt": null,
"disconnectReason": null,
"createdAt": "2026-08-16T10:00:00Z",
"updatedAt": "2026-09-09T14:24:00Z"
}Ciclo de vida del estado
Cada número reporta uno de siete estados. Lee el estado, no la presencia de un número emparejado, para decidir qué hacer a continuación.
| Estado | Significado |
|---|---|
| Emparejamiento pendiente | QR emitido, esperando el escaneo. |
| Conectando | Handshake en curso: normal, no es un fallo. |
| Conectado | Activo y facturable; enviando y recibiendo. |
| Reconexión necesaria | La sesión terminó: reconecta el número. |
| Sin verificar | Escaneado pero la verificación de lectura falló: reintenta. |
| Número incorrecto | Se emparejó un número diferente: empieza de nuevo. |
| Desconectado | No activo. |
Conectar un número
Crea el canal con el número que quieres conectar. Recibes un objeto qr: qr.data es una cadena para codificar y mostrar como código QR (usa cualquier librería de QR), y qr.expiresAt te indica cuándo obtener uno nuevo. El número permanece en PENDING_PAIRING hasta que se escanea.
curl -X POST https://api.sendveo.com/v1/numbers \
-H "X-API-KEY: sv_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "declaredNumber": "+212661234567", "displayName": "Sales line" }'{
"channel": { "id": "chn_sales01", "status": "PENDING_PAIRING", "declaredNumber": "+212661234567" },
"qr": {
"data": "2@r1x8...,K3f...,c9...,a2...",
"expiresAt": "2026-09-09T14:24:20Z"
}
}Actualizar el QR
Los códigos de emparejamiento de WhatsApp rotan cada pocos segundos. Mientras el número siga en PENDING_PAIRING, obtén un qr nuevo en (o justo antes de) expiresAt. Una vez que el número ya no espera un escaneo, esto devuelve 409.
{
"channel": { "id": "chn_sales01", "status": "PENDING_PAIRING", "declaredNumber": "+212661234567" },
"qr": { "data": "2@n7q2...,P0d...,e4...,b8...", "expiresAt": "2026-09-09T14:24:40Z" }
}Verificar el emparejamiento
Un escaneo por sí solo no es una conexión verificada. Consulta verify de forma periódica mientras el número está pendiente: Sendveo espera (devolviendo el número sin cambios) hasta que la sesión se estabiliza, luego lee el número emparejado y lo promueve fail-closed: pasa a CONNECTED solo si el número escaneado coincide con el que declaraste, de lo contrario WRONG_NUMBER_SCANNED o PAIRING_UNVERIFIED.
curl -X POST https://api.sendveo.com/v1/numbers/chn_sales01/verify \
-H "X-API-KEY: sv_live_your_key_here"Reconectar y desconectar
Si una sesión muere más tarde, el número pasa a CREDENTIALS. Reconecta para obtener un qr nuevo que mostrar. Desconectar detiene la línea de inmediato.
Salud
Cada número lleva un objeto health junto a su estado. lastSeenAt es cuándo el servicio de mensajería informó por última vez de que el teléfono estaba accesible, y lastEventAt es cuándo ocurrió el evento de salud más reciente (su propio occurredAt, no cuándo lo guardamos).
{
"id": "chn_sales01",
"declaredNumber": "+212661234567",
"status": "CONNECTED",
"health": {
"phoneOnline": null,
"lastSeenAt": null,
"lastEventAt": "2026-09-16T10:05:00Z"
}
}phoneOnline tiene tres valores, y null no es false. null significa que nunca nos han dicho nada del teléfono, que es el estado de la mayoría de los números: no hay ninguna señal dedicada a si el teléfono está accesible, y Sendveo no va a deducirla del estado de la sesión. Muestra true como en línea, false como desconectado y null como nada en absoluto. Un panel que dibuja null como desconectado pone un aviso en todos los números sanos que tiene.
Perder la sesión y perder el teléfono son incidentes distintos con respuestas distintas. Una sesión muerta necesita a una persona y un escaneo nuevo; un teléfono cargando en la habitación de al lado no necesita nada. Por eso son eventos separados, filas separadas y campos separados.
Cambios recientes
El registro de lo que le ha pasado a un número, de lo más reciente a lo más antiguo. type es el mismo nombre que usan el webhook y el stream del panel, así que un manejador escrito para el webhook lee esta lista sin cambios. limit va de 1 a 200 y por defecto es 50.
{
"numberId": "chn_sales01",
"events": [
{ "id": "cev_m8k3x7q2", "numberId": "chn_sales01", "type": "number.reconnected",
"reason": null, "occurredAt": "2026-09-16T10:12:00Z" },
{ "id": "cev_m8k3wp41", "numberId": "chn_sales01", "type": "number.disconnected",
"reason": "SESSION_EXPIRED", "occurredAt": "2026-09-16T10:05:00Z" }
],
"cursor": null
}reason solo viaja en number.disconnected, y es vocabulario propio de Sendveo, no la palabra de estado del servicio de mensajería: SESSION_EXPIRED (el enlace murió y hace falta un escaneo nuevo) o REMOVED_UPSTREAM (la cuenta se eliminó en WhatsApp).
La paginación es por clave, no por desplazamiento: un registro crece por arriba, así que una página dos calculada por desplazamiento volvería a mostrar filas en cuanto un evento nuevo cayera entre las dos peticiones. Devuelve el id del último evento de la página anterior como cursor. Una página llena ofrece un cursor; una página corta es el final y ofrece null. Un cursor que no es de este número devuelve 400 VALIDATION_FAILED en vez de una primera página silenciosa.
No se registra nada para un número que nunca terminó de vincularse: un fallo ahí es un emparejamiento que no funcionó, no una línea que se cae. Ningún evento se repite, así que un número no parece haberse caído tres veces porque un webhook llegó tres veces. Y la primera vez que nos dicen que un teléfono está accesible no se anuncia nada, porque recuperarse de una avería que nadie vio se lee como ruido.
El perfil de la línea
Lo que tu propia línea publica sobre sí misma: el nombre que ven tus clientes, la frase de estado debajo, si es una cuenta Business y su foto. El motivo para preguntarlo es concreto: conectaste un número que te dijeron que era la línea comercial y quieres ver desde Sendveo que de verdad es la cuenta que crees antes de apuntarle una integración.
{
"profile": {
"numberId": "chn_sales01",
"phone": "+212661234567",
"displayName": "Atlas Traiteur",
"about": "Deliveries 7 days a week",
"business": true,
"pictureUrl": "https://api.sendveo.com/v1/numbers/chn_sales01/picture",
"editable": false
}
}Es de solo lectura, y editable: false lo dice en el recurso. Aquí no hay PATCH, PUT ni POST, y esas rutas devuelven 404: el servicio de mensajería no publica ningún verbo de escritura de perfil para un dispositivo vinculado, y un endpoint que fallara siempre, o que tuviera éxito sin cambiar nada, sería peor que decir esto claramente. Cambia el perfil en WhatsApp, en el teléfono.
phone es el número emparejado y verificado, tal como lo confirmó la verificación de lectura. pictureUrl es una URL de Sendveo direccionada por el id del canal y no por el número de teléfono, así que tu propia línea nunca acaba en un historial de navegador, en un Referer ni en un log de proxy. Es null cuando la línea no publica foto, y la ruta de la foto responde 404 igual para una línea sin foto, para una que la oculta y para un tropiezo pasajero del servicio de mensajería.
Un número que no está conectado, o que aún no está verificado, responde 409 CONFLICT.