Envoyez votre premier message en cinq minutes.
L'API REST travaille sur les numéros que vous avez liés dans la messagerie. Listez-les, trouvez une discussion, envoyez, et suivez chaque statut grâce à des webhooks signés.
- URL de base
- https://api.wrelay.site/v1
- Authentification
- Bearer rl_live_…
- Erreurs
- application/problem+json
- Spécification
- OpenAPI 3.1
Démarrage rapide
Cinq étapes, de la clé API au message distribué. Choisissez un langage une fois : tous les exemples suivent.
Créez une clé API
Dans le tableau de bord, ouvrez Paramètres › Développeurs › Clés API et créez une clé. Elle agit pour toute l'organisation et ne s'affiche qu'une fois : nous n'en gardons que l'empreinte.
Les scopes ne se cumulent pas. Ce démarrage rapide demande
readetsend; enregistrer un endpoint webhook via l'API demandeadmin.Authorization: Bearer rl_live_…# Settings › Developers › API keys › Create keyexport RELAY_API_KEY="rl_live_…"Avec une clé absente, révoquée ou expirée401{"type": "urn:relay:problem:unauthorized","title": "Authentication required","status": 401,"code": "unauthorized","detail": "Send a valid API key as `Authorization: Bearer rl_live_…`."}Listez vos numéros
GET /v1/numbersrenvoie les numéros de l'organisation, du plus ancien au plus récent. Gardez l'idd'un numéro dont lestatusvautworking:can_sendindique qu'il est prêt.Les listes sont paginées avec
limitetcursor: renvoyeznext_cursorjusqu'à ce qu'il vaillenull.GET/v1/numberscurl "https://api.wrelay.site/v1/numbers?status=working" \-H "Authorization: Bearer $RELAY_API_KEY"Réponse200{"data": [{"id": "num_01a1a1c6c2007a3f9c41e2b7d5a60813","label": "Sales","phone": "+212600000001","display_name": "Atelier Exemple","avatar_url": null,"status": "working","status_reason": null,"status_changed_at": "2026-11-10T08:02:11.000Z","first_connected_at": "2026-11-03T14:20:05.000Z","connected_at": "2026-11-10T08:02:11.000Z","last_event_at": "2026-11-12T09:11:57.000Z","can_send": true,"daily_send_cap": 400,"history_import": {"status": "completed","started_at": "2026-11-03T14:20:09.000Z","finished_at": "2026-11-03T14:31:52.000Z","chats_total": 214,"chats_done": 214,"messages": 18342,"error_code": null},"created_at": "2026-11-03T14:18:40.000Z"}],"has_more": false,"next_cursor": null}Trouvez ou ouvrez une discussion
Cherchez parmi les discussions d'un numéro par nom ou par chiffres du téléphone, et gardez l'
idde la discussion.Pas encore de conversation ? Passez cette étape et envoyez avec
number_idettoau lieu dechat_id: la discussion s'ouvre avec le premier message.GET/v1/chatscurl "https://api.wrelay.site/v1/chats?number_id=num_01a1a1c6c2007a3f9c41e2b7d5a60813&search=Maison" \-H "Authorization: Bearer $RELAY_API_KEY"Réponse200{"data": [{"id": "chat_01a1a1d0f4a87c128e5d4b9a2f61c37e","number_id": "num_01a1a1c6c2007a3f9c41e2b7d5a60813","type": "dm","jid": "212612345678@s.whatsapp.net","phone": "+212612345678","name": "Maison Exemple","description": null,"avatar_url": null,"unread_count": 1,"last_message_at": "2026-11-12T09:10:03.000Z","archived": false,"pinned": false,"pinned_at": null,"muted": false,"muted_until": null,"left_at": null,"only_admins_send": false,"only_admins_edit": false,"group_created_at": null,"created_at": "2026-11-03T14:24:17.000Z"}],"has_more": false,"next_cursor": null}Envoyez un message
POST /v1/messagesenregistre le message enpendinget le met en file. Il part sur WhatsApp en quelques secondes, à quelques secondes d'intervalle par numéro.Relancez un appel en échec ou expiré avec la même
Idempotency-Keyet le même corps : vous récupérez le même message avec son statut actuel, jamais un second envoi.POST/v1/messagescurl https://api.wrelay.site/v1/messages \-H "Authorization: Bearer $RELAY_API_KEY" \-H "Idempotency-Key: $(uuidgen)" \-H "Content-Type: application/json" \-d '{"chat_id": "chat_01a1a1d0f4a87c128e5d4b9a2f61c37e", "type": "text","text": "Friday works, your order is updated."}'Réponse202{"id": "msg_01a1cf07d7407d84b2a619f3c8e05d47","status": "pending","chat_id": "chat_01a1a1d0f4a87c128e5d4b9a2f61c37e"}Recevez les webhooks
Ajoutez un endpoint pour
message.receivedetmessage.status, dans le tableau de bord ou avec une clé qui a le scopeadmin. La réponse contient le secret de signature, une seule fois.Chaque événement arrive ensuite en POST signé. Répondez par un 2xx en moins de 10 secondes, et vérifiez la signature avant de vous fier au corps.
POST/v1/webhooks/endpointscurl https://api.wrelay.site/v1/webhooks/endpoints \-H "Authorization: Bearer $RELAY_ADMIN_KEY" \-H "Idempotency-Key: $(uuidgen)" \-H "Content-Type: application/json" \-d '{"url": "https://example.com/hooks/relay","events": ["message.received", "message.status"]}'Réponse201{"id": "whk_01a1c5e0a9407e8db0c27f4a19d3e65b","url": "https://example.com/hooks/relay","description": null,"events": ["message.received","message.status"],"number_ids": [],"enabled": true,"disabled_at": null,"disabled_reason": null,"consecutive_failures": 0,"last_delivery_at": null,"last_success_at": null,"created_at": "2026-11-12T09:05:31.000Z","secret": "whsec_…"}Sur votre endpointPOST /hooks/relayContent-Type: application/jsonUser-Agent: relay-webhooks/1X-Relay-Event: message.statusX-Relay-Delivery: dlv_01a1cf07f1a27b6e9d20c3a85e17f4b9X-Relay-Timestamp: 1794474764X-Relay-Signature: v1=…{"id": "evt_01a1cf07f1a27b6e9d20c3a85e17f4b9","event": "message.status","created_at": "2026-11-12T09:12:44.120Z","data": {"id": "msg_01a1cf07d7407d84b2a619f3c8e05d47","number_id": "num_01a1a1c6c2007a3f9c41e2b7d5a60813","chat_id": "chat_01a1a1d0f4a87c128e5d4b9a2f61c37e","wa_message_id": "3EB0A7C2F51D9E84B6C3","from_me": true,"client_key": "3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80","status": "delivered","error_code": null,"sent_at": "2026-11-12T09:12:41.000Z"}}
Comment se comporte l'API
Les règles communes à tous les endpoints. La référence détaille chacun d'eux.
Identifiants publics
Chaque identifiant se compose d'un préfixe de ressource et de 32 caractères hexadécimaux. Un identifiant d'une autre organisation, mal formé ou au mauvais préfixe répond 404 <resource>_not_found, jamais une erreur distincte.
num_Numérochat_Discussion ou groupemsg_Messagect_Contactupl_Téléversementmedia_Pièce jointe reçuewhk_Endpoint webhookdlv_Livraison webhookevt_Événement webhookop_Opération
Scopes
Une clé porte un ou plusieurs scopes. Un appel hors de ces scopes répond 403 insufficient_scope ; GET /v1/operations/{id} accepte n'importe quelle clé.
readTous les GET : numéros, discussions, messages, groupes, contacts et téléchargement des médias.sendEnvois, réactions, modifications et suppressions, état des discussions (archiver, épingler, mettre en sourdine, marquer comme lu), groupes, téléversements et vérification de numéros.adminConnexion, association, déconnexion et suppression des numéros ; endpoints webhook et journal des livraisons.Les scopes ne se cumulent pas : donnez à une clé chacun des scopes dont elle a besoin.
Idempotence
Chaque POST, PATCH et DELETE exige un en-tête Idempotency-Key : de 1 à 150 caractères ASCII visibles, idéalement un UUID.
- Même clé et même corps sous 24 heures : la réponse d'origine revient avec
Idempotent-Replayed: true, et rien ne se produit deux fois. - Même clé pour une autre requête :
422 idempotency_key_reused. Premier appel encore en cours :409 idempotency_key_in_use. - Seules les réponses réussies sont conservées : après une erreur, la même clé relance la requête.
- Pour les envois, la clé est aussi le
client_keydu message : relancerPOST /v1/messages, même après 24 heures, renvoie le même message avec son statut actuel. - Une exception :
POST /v1/numbers/{id}/pairing-codene prend pas de clé, car son code n'est jamais conservé.
Limites de débit
Chaque clé a un plafond de rafale et un budget par minute. Chaque numéro et chaque organisation ont en plus leurs propres limites.
rate_limitedrate_limitedrate_limitedquota_exceededquota_exceededsend_cap_reachedrecipient_never_wroteChaque réponse authentifiée porte RateLimit-Limit, RateLimit-Remaining et RateLimit-Reset (en secondes) pour la fenêtre la plus serrée, et RateLimit-Policy pour toutes. Un 429 ajoute Retry-After.
Erreurs
Toute réponse hors 2xx est un problème RFC 9457, servi en application/problem+json. Basez-vous sur code : les codes ne sont jamais renommés ni retirés, et de nouveaux peuvent apparaître. errors liste les champs à corriger.
idempotency_key_requiredAjoutez un en-tête Idempotency-Key à l'écriture.unauthorizedVérifiez la clé : inconnue, révoquée ou expirée, elle répond la même chose.insufficient_scopeUtilisez une clé qui a le scope de l'endpoint.chat_not_foundL'identifiant n'appartient pas à cette organisation, ou son préfixe est faux.number_not_workingLe numéro n'est pas connecté : attendez que number.status indique working.validation_failedCorrigez les champs listés dans errors.rate_limitedAttendez Retry-After secondes, puis réessayez.gateway_<reason>WhatsApp n'a pas répondu à un appel immédiat : réessayez sous peu.enqueue_failedEnregistré mais pas mis en file : réessayez avec la même Idempotency-Key.Opérations longues
Les appels qui attendent WhatsApp (connecter ou supprimer un numéro, commandes de groupe, vérification de numéros) répondent 202 avec un operation_id et un en-tête Location.
- Interrogez
GET /v1/operations/{id}toutes les 1 à 2 secondes jusqu'à ce questatusvaillesucceeded(lisezresult) oufailed(lisezerror.code), puis relisez la ressource. - Une opération qui ne se termine pas dans l'heure passe en
failed, avec le codeexpired.
Envoyer des fichiers
Les fichiers ne transitent jamais par l'API : ils vont directement dans le stockage, puis le message y fait référence.
Images et audio jusqu'à 16 Mo, vidéo jusqu'à 64 Mo, documents jusqu'à 100 Mo. Une pièce jointe reçue se télécharge avec GET /v1/media/{id}, qui répond une URL signée valable 5 minutes.
POST /v1/media/uploadsavecmime_typeetsize_bytes: vous obtenez un identifiantupl_et uneupload.urlprésignée.- Envoyez les octets bruts en
PUTsurupload.urlavec exactementupload.headerset sans en-tête Authorization, dans les 15 minutes. POST /v1/media/uploads/{id}/finalize: le téléversement passe àready.POST /v1/messagesavectype,upload_idet une légendetextfacultative, dans les 24 heures.
Chaque endpoint, dans la référence.
La référence en ligne et le fichier OpenAPI 3.1 sont générés à partir des schémas qui valident vos requêtes : ils ne peuvent pas diverger de ce que l'API accepte.