Numéros
Un numéro (un « canal ») est une ligne WhatsApp connectée à votre compte. En connecter un est un flux fail-closed : vous déclarez le numéro, affichez le QR que nous renvoyons et le scannez depuis ce téléphone, et Sendveo relit le numéro appairé avant même de le marquer comme connecté. Le QR vous appartient : il n'y a aucune page externe ni redirection.
L'objet numéro
{
"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"
}Cycle de vie du statut
Chaque numéro rapporte l'un de sept états. Lisez le statut, et non la présence d'un numéro appairé, pour décider de la suite.
| Statut | Signification |
|---|---|
| Appairage en attente | QR émis, en attente du scan. |
| Connexion en cours | Établissement de la connexion en cours : normal, pas un échec. |
| Connecté | Actif et facturable ; envoi et réception. |
| Reconnexion requise | Session terminée : reconnectez le numéro. |
| Non vérifié | Scanné mais la relecture a échoué : réessayez. |
| Mauvais numéro | Un numéro différent a été appairé : recommencez. |
| Déconnecté | Inactif. |
Connecter un numéro
Créez le canal avec le numéro que vous souhaitez connecter. Vous récupérez un objet qr : qr.data est une chaîne à encoder et afficher sous forme de QR code (utilisez n'importe quelle bibliothèque QR), et qr.expiresAt vous indique quand en récupérer un nouveau. Le numéro reste PENDING_PAIRING jusqu'à ce qu'il soit scanné.
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"
}
}Rafraîchir le QR
Les codes d'appairage WhatsApp changent toutes les quelques secondes. Tant que le numéro est encore PENDING_PAIRING, récupérez un nouvel objet qr à (ou juste avant) expiresAt. Une fois que le numéro n'attend plus de scan, cela renvoie 409.
{
"channel": { "id": "chn_sales01", "status": "PENDING_PAIRING", "declaredNumber": "+212661234567" },
"qr": { "data": "2@n7q2...,P0d...,e4...,b8...", "expiresAt": "2026-09-09T14:24:40Z" }
}Vérifier l'appairage
Un scan seul n'est pas une connexion vérifiée. Interrogez verify à intervalle régulier tant que le numéro est en attente : Sendveo patiente (en renvoyant le numéro inchangé) jusqu'à ce que la session se stabilise, puis relit le numéro appairé et le promeut en fail-closed : il ne devient CONNECTED que si le numéro scanné correspond à celui que vous avez déclaré, sinon WRONG_NUMBER_SCANNED ou PAIRING_UNVERIFIED.
curl -X POST https://api.sendveo.com/v1/numbers/chn_sales01/verify \
-H "X-API-KEY: sv_live_your_key_here"Reconnecter & déconnecter
Si une session meurt plus tard, le numéro passe à CREDENTIALS. Reconnectez-le pour obtenir un nouvel objet qr à afficher. La déconnexion arrête la ligne immédiatement.
Santé
Chaque numéro porte un objet health à côté de son statut. lastSeenAt est le moment où le service de messagerie a signalé pour la dernière fois que le téléphone était joignable, et lastEventAt celui où le dernier événement de santé s'est produit (son propre occurredAt, pas la date à laquelle nous l'avons stocké).
{
"id": "chn_sales01",
"declaredNumber": "+212661234567",
"status": "CONNECTED",
"health": {
"phoneOnline": null,
"lastSeenAt": null,
"lastEventAt": "2026-09-16T10:05:00Z"
}
}phoneOnline a trois valeurs, et null n'est pas false. null signifie qu'on ne nous a jamais rien dit sur le téléphone, ce qui est l'état de la plupart des numéros : il n'existe aucun signal dédié à la joignabilité du téléphone, et Sendveo n'en déduira pas un depuis le statut de session. Affichez true comme en ligne, false comme hors ligne, et null comme rien du tout. Un tableau de bord qui dessine null comme hors ligne colle un avertissement sur chacun de ses numéros en bonne santé.
Perdre la session et perdre le téléphone sont deux incidents différents, aux réponses différentes. Une session morte exige une personne et un nouveau scan ; un téléphone en charge dans la pièce d'à côté n'exige rien. C'est pour cela que ce sont des événements distincts, des lignes distinctes et des champs distincts.
Changements récents
Le registre de ce qui est arrivé à un numéro, du plus récent au plus ancien. type porte le même nom que dans le webhook et le flux du tableau de bord : un gestionnaire écrit pour le webhook lit cette liste sans modification. limit va de 1 à 200 et vaut 50 par défaut.
{
"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 n'accompagne que number.disconnected, et c'est le vocabulaire propre à Sendveo plutôt que le mot de statut du service de messagerie : SESSION_EXPIRED (le lien est mort, un nouveau scan est nécessaire) ou REMOVED_UPSTREAM (le compte a été supprimé sur WhatsApp).
La pagination se fait par clé, pas par décalage : un registre grandit par le haut, donc une page deux calculée par décalage réafficherait des lignes dès qu'un nouvel événement se glisse entre les deux requêtes. Renvoyez l'id du dernier événement de la page précédente comme cursor. Une page pleine propose un curseur ; une page courte est la fin et propose null. Un curseur qui n'appartient pas à ce numéro donne 400 VALIDATION_FAILED plutôt qu'une première page silencieuse.
Rien n'est enregistré pour un numéro dont la liaison n'a jamais abouti : un échec à ce stade est un appairage qui n'a pas fonctionné, pas une ligne qui tombe. Aucun événement ne se répète, donc un numéro ne semble pas être tombé trois fois parce qu'un webhook est arrivé trois fois. Et la première fois qu'on nous dit qu'un téléphone est joignable, rien n'est annoncé : une reprise après une panne que personne n'a vue se lit comme du bruit.
Le profil de la ligne
Ce que votre propre ligne publie sur elle-même : le nom que vos clients voient, la ligne de statut en dessous, s'il s'agit d'un compte Business, et sa photo. La raison de le demander est concrète : vous avez connecté un numéro qu'on vous a présenté comme la ligne commerciale, et vous voulez voir depuis Sendveo qu'il s'agit bien du compte que vous croyez avant de brancher une intégration dessus.
{
"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
}
}C'est en lecture seule, et editable: false le dit sur la ressource. Il n'y a ici ni PATCH, ni PUT, ni POST, et ces chemins répondent 404 : le service de messagerie ne publie aucun verbe d'écriture de profil pour un appareil relié, et un endpoint qui échouerait à chaque fois, ou qui réussirait sans rien changer, serait pire que de le dire franchement. Modifiez le profil dans WhatsApp, sur le téléphone.
phone est le numéro appairé vérifié, repris de ce que la relecture a confirmé. pictureUrl est une URL Sendveo adressée par l'id du canal plutôt que par le numéro de téléphone : votre propre ligne ne finit jamais dans un historique de navigateur, un Referer ou un journal de proxy. Elle vaut null quand la ligne ne publie aucune photo, et la route de la photo répond 404 aussi bien pour une ligne qui n'en a pas que pour une ligne qui la masque ou un hoquet en amont.
Un numéro non connecté, ou pas encore vérifié, répond 409 CONFLICT.