Messages
Lisez les messages entrants que reçoivent vos numéros, et envoyez les vôtres. Répondez à une conversation lancée par un contact, ou écrivez directement à un numéro de téléphone : Sendveo ouvre la conversation pour vous.
L'objet message
{
"id": "msg_7Qk2",
"channelId": "chn_sales01",
"direction": "INBOUND",
"chatId": "chat_aa11",
"from": "+212670111222",
"to": null,
"senderName": "Khadija Alaoui",
"text": "Here is the model we agreed on.",
"type": "image",
"status": "RECEIVED",
"statusAt": null,
"failureReason": null,
"timestamp": "2026-09-09T14:02:11Z",
"attachments": [
{
"id": "att_5Hq1",
"kind": "IMAGE",
"mimeType": "image/jpeg",
"fileName": "modele.jpg",
"size": 184320,
"durationSeconds": null,
"width": 1080,
"height": 1440,
"voiceNote": false,
"url": "https://api.sendveo.com/v1/messages/msg_7Qk2/attachments/att_5Hq1"
}
],
"replyTo": { "id": "msg_7Qk1", "providerMessageId": "wamid.HB1" },
"location": null,
"contacts": null,
"linkPreview": null,
"reactions": [{ "emoji": "👍", "by": "+212670111222", "at": "2026-09-09T14:03:00Z" }],
"deletedAt": null,
"editedAt": null
}direction vaut INBOUND ou OUTBOUND. from porte l'expéditeur sur les messages entrants et vaut null sur les sortants ; to en est le miroir : il porte le numéro du destinataire sur un message que vous avez envoyé à un numéro de téléphone, et vaut null sinon. status vaut SENT ou FAILED sur les messages sortants ; les messages entrants sont toujours RECEIVED.
Envoyer un message
Chaque envoi indique le numéro expéditeur dans channelId (numberId est accepté comme le même champ), le message dans text, et exactement un destinataire : chatId pour répondre à une conversation existante, ou to pour un numéro de téléphone. Le numéro doit être CONNECTED, sinon la requête renvoie 409.
curl -X POST https://api.sendveo.com/v1/messages \
-H "X-API-KEY: sv_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "channelId": "chn_sales01", "chatId": "chat_aa11", "text": "Yes - shipping tomorrow." }'{
"message": {
"id": "msg_8Rm5",
"channelId": "chn_sales01",
"direction": "OUTBOUND",
"chatId": "chat_aa11",
"to": null,
"text": "Yes - shipping tomorrow.",
"status": "SENT",
"timestamp": "2026-09-09T14:05:40Z"
}
}Envoyer à un numéro de téléphone
Utilisez to avec un numéro de téléphone au format E.164 pour écrire à quelqu'un qui ne vous a jamais écrit : Sendveo ouvre la conversation et renvoie son chatId, si bien que les réponses arrivent dans le même fil. +212661234567, 212661234567 et 00212661234567 sont tous acceptés, avec ou sans espaces, tirets ou parenthèses.
curl -X POST https://api.sendveo.com/v1/messages \
-H "X-API-KEY: sv_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "channelId": "chn_sales01", "to": "+212661234567", "text": "Your order is ready." }'{
"message": {
"id": "msg_9Tn7",
"channelId": "chn_sales01",
"direction": "OUTBOUND",
"chatId": "chat_9Fp2",
"to": "+212661234567",
"text": "Your order is ready.",
"status": "SENT",
"timestamp": "2026-09-15T09:12:03Z"
}
}Un envoi vers un numéro qui a déjà une conversation poursuit cette conversation au lieu d'en ouvrir une seconde, que le contact ait écrit en premier ou vous. Une même personne ne se retrouve jamais répartie sur deux fils.
Un to qui n'est pas un numéro de téléphone exploitable, chatId et to ensemble, ou aucun des deux : dans les trois cas la requête renvoie 400 VALIDATION_FAILED en nommant le champ dans details, et rien n'est envoyé. Une forme nationale commençant par un zéro comme 0661234567 est refusée : sans indicatif pays, ce n'est qu'une supposition.
Restez mesuré sur les premiers contacts : WhatsApp bloque les numéros qui écrivent à beaucoup de gens sans avoir été sollicités, et c'est votre propre ligne professionnelle qui se retrouve bloquée. Sendveo autorise 20 nouvelles conversations par numéro et par heure, puis répond 429 OUTBOUND_LIMIT avec un Retry-After au-delà. Les réponses ne sont jamais comptées.
Pièces jointes
Un message peut porter jusqu'à 10 fichiers. Envoyez les octets en multipart/form-data, ou passez une url publique en JSON et Sendveo récupère le fichier pour vous. Fichiers et URL ne peuvent pas être mélangés dans un même message. text est facultatif dès lors que le message porte un fichier : une photo sans légende est un vrai message.
Dans une requête multipart, les fichiers passent par attachments (répétez le champ pour en envoyer plusieurs), et un fichier audio que vous voulez afficher sous forme d'onde va plutôt sur voice_note. Tout autre nom de champ de fichier renvoie 400.
curl -X POST https://api.sendveo.com/v1/messages \
-H "X-API-KEY: sv_live_your_key_here" \
-F channelId=chn_sales01 \
-F to=+212661234567 \
-F "text=Voici le devis" \
-F "attachments=@devis.pdf;type=application/pdf"{
"channelId": "chn_sales01",
"chatId": "chat_aa11",
"attachments": [
{
"url": "https://cdn.example.com/devis.pdf",
"mimeType": "application/pdf",
"filename": "devis.pdf"
}
]
}Limites
Ce sont les limites de WhatsApp, pas les nôtres : un fichier qui les dépasse est refusé par WhatsApp après l'envoi, donc Sendveo le refuse en amont et nomme la limite. Un type absent de la liste est refusé par son nom. Une erreur de pièce jointe est un 400 VALIDATION_FAILED avec details[0].path = "attachments.N.…" : un appelant qui envoie cinq fichiers sait lequel a été refusé, et rien n'atteint le service de messagerie.
| Type | Types de média | Plafond |
|---|---|---|
| image | image/jpeg image/png image/gif image/webp | 5 MB |
| video | video/mp4 video/3gpp video/quicktime | 16 MB |
| audio | audio/mpeg audio/mp4 audio/aac audio/amr audio/ogg audio/wav | 16 MB |
| voice note | as audio, plus audio/opus audio/webm | 16 MB |
| document | application/pdf, the Office types, application/zip, application/json, text/plain, text/csv, text/vcard | 100 MB |
| sticker | image/webp | 500 KB |
Télécharger une pièce jointe
Le point de terminaison renvoie les octets, avec la même authentification que le reste de l'API. C'est un proxy, jamais une redirection. Sendveo ne stocke aucun octet : le fichier est récupéré à la demande, transmis en flux puis oublié, donc chaque téléchargement est un nouvel appel et une pièce jointe n'est téléchargeable que tant que le service de messagerie la conserve. Un fichier que vous avez envoyé ne peut pas être retéléchargé (409) : ses métadonnées figurent sur le message, pour que votre propre interface affiche ce que vous avez envoyé.
Réponses et réactions
Citez un message avec replyTo : un identifiant Sendveo msg_…, ou l'identifiant fournisseur d'un message antérieur à la connexion, qui est accepté et transmis tel quel. Dans ce second cas, le message renvoyé porte replyTo avec id: null.
Une réaction est un seul emoji natif. Du texte donne 400. Réagir à nouveau remplace votre réaction, cela n'en ajoute jamais une deuxième, et { "emoji": null } (ou DELETE …/reactions, la même opération) la retire. Le champ reactions d'un message porte les réactions en vigueur à l'instant, pas un journal ; la réaction du numéro connecté apparaît avec by: null.
curl -X POST https://api.sendveo.com/v1/messages/msg_8Rm5/reactions \
-H "X-API-KEY: sv_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "emoji": "👍" }'Transfert et suppression
Un transfert répond 201 avec la copie enregistrée dans la conversation de destination, si bien que votre propre boîte de réception montre ce que vous avez envoyé. { to: "+212661234567" } transfère dans la conversation que vous avez déjà avec ce numéro ; un transfert vers un numéro avec lequel il n'existe aucune conversation renvoie 409, et non une nouvelle conversation : faire d'un transfert un premier contact à froid dépenserait la réputation du numéro sans que vous l'ayez demandé.
curl -X POST https://api.sendveo.com/v1/messages/msg_8Rm5/forward \
-H "X-API-KEY: sv_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "chatId": "chat_aa11" }'La suppression retire le message chez WhatsApp. Côté Sendveo, la ligne est conservée : text est vidé, attachments devient [] et deletedAt est renseigné, de sorte qu'un historique montre encore que quelque chose a été dit puis retiré. Supprimer un message déjà supprimé renvoie 409.
Réagir, transférer, supprimer pour tout le monde et marquer comme lu exigent la portée messages:manage, qui ne fait délibérément PAS partie de messages:send : agir sur un message qui existe déjà est une autorité différente que d'en écrire un nouveau. Les clés émises avant l'existence de cette portée ne la portent pas et répondent 403 : émettez une nouvelle clé plutôt que de modifier l'ancienne. POST /v1/chats/:chatId/typing ne demande que messages:send.
Positions et contacts
Une position est stockée de façon structurée et renvoyée sous forme de nombres dans les deux sens. Une valeur hors plage donne 400 en nommant location.latitude ou location.longitude. Votre text est conservé au-dessus.
Chaque téléphone d'une fiche de contact est normalisé selon la même règle qu'un destinataire, et une forme nationale commençant par un zéro est refusée. Les fiches de contact entrantes sont réanalysées en noms et en numéros E.164 pour vous. Une position et des contacts ne peuvent pas voyager sur le même message.
{
"channelId": "chn_sales01",
"chatId": "chat_aa11",
"text": "On est ici",
"location": {
"latitude": 33.5731,
"longitude": -7.5898,
"name": "Casablanca",
"address": "Boulevard Zerktouni"
}
}
{
"channelId": "chn_sales01",
"chatId": "chat_aa11",
"contacts": [
{ "name": "Nadia Benali", "phones": ["+212661778899"], "organisation": "Atlas" }
]
}Aperçus de liens
linkPreview vaut true par défaut : le premier lien http(s) du texte est renvoyé sous la forme linkPreview: { url }, ce qui vous évite de réanalyser le corps du message. Sendveo ne récupère jamais le lien : pas de titre, pas d'image, aucune requête sortante par envoi.
Ce que l'option ne fait pas : la carte d'aperçu que voit un destinataire est dessinée par son client WhatsApp à partir de l'URL présente dans le texte, et aucune primitive ne permet de la supprimer. linkPreview: false empêche seulement Sendveo d'extraire le lien ; cela ne supprime pas la carte de WhatsApp, et le texte n'est jamais réécrit pour essayer.
Marquer comme lu et saisie
Marquer une conversation comme lue et afficher un indicateur de saisie tiennent chacun en un seul appel. L'indicateur de saisie est « au mieux » par contrat : il répond 200 { "sent": false } lorsque le service de messagerie le refuse, plutôt que de faire échouer votre requête ; une politesse ne doit pas faire échouer un appelant. Ce qui reste une erreur, c'est votre propre faute : une conversation inconnue (404), un numéro non connecté (409), une durée hors de 1-25000 ms (400).
Statut de remise
Sur un message sortant, status parcourt SENT → DELIVERED → READ, et il ne progresse que vers l'avant : les accusés arrivent dans le désordre, et un DELIVERED tardif ne rend jamais un message non lu. FAILED l'emporte sur tout et rien ne l'emporte sur lui. statusAt indique le dernier changement, et failureReason n'est renseigné qu'avec FAILED.
Lister les messages
Filtrez par numéro, direction et date. Les résultats sont classés du plus récent au plus ancien et limités à 200 par page.
curl "https://api.sendveo.com/v1/messages?channelId=chn_sales01&direction=INBOUND&limit=50" \
-H "X-API-KEY: sv_live_your_key_here"