Sur cette page
Une importation de contact peut renvoyer 201 alors que l’intégration est erronée : identifiant source perdu, mauvaise liste choisie ou désabonnement antérieur ignoré. Ce sont des décisions sur la propriété des données, pas des détails HTTP.
Ce guide fait fonctionner une requête Brevo, puis montre les vérifications nécessaires pour une petite synchronisation reproductible.
Ce que couvre l’API Brevo
Tout se trouve sous un seul hôte et un seul chemin de version. La documentation développeur regroupe la surface en quatre domaines produit :
- Messagerie : e-mail transactionnel, SMS et WhatsApp, y compris les envois par lot, la programmation et l’activité des messages.
- Plateforme marketing : contacts, listes, segments et campagnes e-mail.
- E-commerce : produits, commandes et suivi des événements clients.
- Conversations : le widget de chat et la gestion programmatique des conversations.
Ces domaines partagent un même compte, une même base de contacts et une même clé API. C’est pratique, et parfois dangereux : un script écrit avec une idée d’environnement de test en tête s’adresse aux contacts auxquels vos campagnes envoient réellement.
Transactionnel contre marketing
Les deux familles se comportent de façon suffisamment différente pour que les confondre soit l’erreur de conception la plus fréquente.
| Transactionnel | Marketing | |
|---|---|---|
| Endpoint principal | POST /v3/smtp/email | POST /v3/emailCampaigns |
| Adressage | Destinataires explicites dans la requête | listIds ou segmentIds |
| Déclencheur | Votre application, en temps réel | Programmé ou envoyé à la demande |
| Profil de volume typique | Continu, un message à la fois | En rafale, un gros envoi |
| Posture de limite de débit | Très élevée, 1 000 requêtes par seconde sur le palier général | Faible, les endpoints de campagne tombent sous le plafond général |
Si vous hésitez encore à savoir si Brevo est la bonne plateforme, la présentation de la plateforme traite ce sujet.
Authentification et gestion des clés
Pour une clé API standard, Brevo utilise l’en-tête api-key ; certaines opérations acceptent aussi un jeton OAuth bearer. Cet en-tête s’appelle api-key, pas Authorization, et il n’y a aucun préfixe Bearer. C’est ce qui fait trébucher presque toutes les personnes venant d’une autre API de messagerie.
OAuth peut utiliser un jeton Bearer pour les opérations compatibles ; une clé API standard reste dans l’en-tête api-key.
curl https://api.brevo.com/v3/account \ -H "api-key: $BREVO_API_KEY"Les clés se génèrent dans l’application Brevo, dans les paramètres du compte, section SMTP et API, onglet clés API. Donnez à chaque clé un nom descriptif lié au système qui l’utilise. La valeur de la clé s’affiche exactement une fois au moment de sa génération : si vous la perdez, vous en générez une nouvelle plutôt que de récupérer l’ancienne.
Quelques règles pratiques :
- Émettez une clé distincte par environnement de déploiement et par service. Révoquer une clé compromise ne doit jamais mettre à l’arrêt trois systèmes sans rapport entre eux.
- Les clés API standard couvrent tout le compte. Considérez toute clé comme un accès complet aux contacts, à l’envoi et aux données CRM.
- Brevo prend aussi en charge OAuth 2.0 pour les applications qui agissent au nom d’autres comptes Brevo, décrit à côté du flux par clé dans les schémas d’authentification.
- Le serveur MCP utilisé par les assistants IA prend un jeton distinct et utilise, lui, un en-tête bearer. Ce jeton se génère dans le même écran de clés API mais n’est pas interchangeable avec une clé REST.
URL de base, versionnement et première écriture
L’URL de base est https://api.brevo.com/v3/. La version se trouve dans le chemin plutôt que dans un en-tête, et v3 est la génération actuelle. Tous les chemins de ce guide sont relatifs à cette base.
Pour la première écriture de contact, utilisez une adresse de test sous votre contrôle, un véritable identifiant de liste et un identifiant source stable. Créez d’abord l’attribut FNAME dans Brevo. Avec une vraie clé, cet exemple écrit dans le compte :
curl -X POST https://api.brevo.com/v3/contacts \ -H "api-key: $BREVO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "email": "[email protected]", "ext_id": "test-source-contact-1", "attributes": { "FNAME": "Dev" }, "listIds": [42], "updateEnabled": true, "getId": true }'Remplacez 42 par votre liste de test. Relisez le contact et vérifiez son identifiant, le prénom, la liste et les blocages. Un code 2xx seul ne prouve pas la synchronisation attendue.
Les endpoints que vous utiliserez vraiment
Contacts
POST /v3/contacts crée un contact. Le corps accepte email, des attributes déclarés, listIds et ext_id comme identifiant source stable. updateEnabled: true met à jour un contact existant et getId: true demande son identifiant final. Brevo documente ces champs. Réutilisez le même ext_id à chaque exécution et conservez la correspondance entre les identifiants source et Brevo pour les réconciliations.
Avant la première écriture, décidez quel système possède chaque attribut et quelle liste reçoit les contacts consentants. Indiquez explicitement listIds. Ne définissez jamais emailBlacklisted: false dans une mise à jour ordinaire. Brevo prévient que le changement d’adresse d’un contact bloqué lève le blocage. Traitez ce changement séparément. Vérifiez les blocages et les désabonnements avant tout ajout à une liste marketing. Une course entre lecture et écriture reste possible ; une dernière vérification avant l’envoi est nécessaire si la garantie doit être stricte.
Les lectures passent par GET /v3/contacts, qui pagine avec limit (50 par défaut, 1000 au maximum) et offset, et prend en charge modifiedSince et createdSince en UTC. Les synchronisations incrémentales doivent s’appuyer sur modifiedSince plutôt que de parcourir la liste entière. Notez que le paramètre filter ne prend en charge qu’un opérateur d’égalité : tout ce qui demande plus d’expressivité appartient à un segment.
Pour le chargement en masse, POST /v3/contacts/import accepte fileUrl, fileBody ou jsonBody, cible des listIds et s’exécute de façon asynchrone en renvoyant un processId. Brevo documente un corps maximal de 10 Mo et recommande de rester autour de 8 Mo, car l’analyse gonfle la charge utile. Fournissez notifyUrl pour connaître le résultat au lieu d’interroger l’API en boucle.
E-mail transactionnel
POST /v3/smtp/email est le cheval de trait. Au-delà de sender, to, subject et htmlContent, les champs à connaître sont :
templateIdavecparams, qui remplace le contenu en ligne par un modèle Brevo et ses substitutions de variables. Les paramètres d’une version individuelle sont plafonnés à 100 Ko, les paramètres cumulés à 1000 Ko.messageVersions, qui envoie des variantes personnalisées en un seul appel, avec jusqu’à 99 destinataires par version.tagspeuvent aider à relier les événements de livraison au flux applicatif qui les a produits.scheduledAtaccompagné debatchId, pour les envois futurs que vous voudrez peut-être annuler en groupe.headers, en Title-Case, pour les en-têtes SMTP personnalisés.
Une seule requête accepte au maximum 2 000 destinataires. Pour la différence entre cet endpoint et l’envoi de campagnes, le guide de l’e-mail transactionnel propose le point de vue stratégique.
Campagnes e-mail
POST /v3/emailCampaigns exige name et sender, plus exactement une source de contenu : htmlContent (10 caractères minimum, moins de 1 Mo), htmlUrl ou templateId. L’audience se met dans recipients sous forme de listIds ou de segmentIds, et scheduledAt utilise le format UTC YYYY-MM-DDTHH:mm:ss.SSSZ. Des routes complémentaires couvrent l’envoi immédiat, l’envoi d’un test, la mise à jour du statut et la récupération du rapport de campagne.
Entreprises, transactions et objets
Le CRM de Brevo comporte deux chemins d’écriture qui se recouvrent, et bien choisir a son importance.
Les routes CRM sont POST /v3/companies, PATCH /v3/companies/{id}, DELETE /v3/companies/{id}, et l’ensemble équivalent pour les transactions. Elles sont synchrones. Un PATCH renvoie un code 204 une fois la modification appliquée.
L’API objets est le chemin de masse : POST /v3/objects/{object_type}/batch/upsert accepte jusqu’à 1000 enregistrements et 1 Mo par requête, jusqu’à 500 attributs par enregistrement et jusqu’à 10 enregistrements d’association par type d’objet et par enregistrement. Elle renvoie un code 202 avec un processId, ce qui signifie accepté et non appliqué.
curl -X POST https://api.brevo.com/v3/objects/company/batch/upsert \ -H "api-key: $BREVO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "records": [ { "identifiers": { "id": 12345 }, "attributes": { "domain": "acme.example", "industry": "retail" } } ] }'Le guide du CRM Brevo traite le modèle d’objets du point de vue opérationnel.
SDK officiels
Brevo maintient des clients sous l’organisation GitHub getbrevo :
| Langage | Dépôt |
|---|---|
| Node.js | github.com/getbrevo/brevo-node |
| Python | github.com/getbrevo/brevo-python |
| PHP | github.com/getbrevo/brevo-php |
| Java | github.com/getbrevo/brevo-java |
| C# | github.com/getbrevo/brevo-csharp |
| Go | github.com/getbrevo/brevo-go |
| Ruby | github.com/getbrevo/brevo-ruby |
Le client Node s’installe sous le nom @getbrevo/brevo :
npm install @getbrevo/brevoimport { BrevoClient } from "@getbrevo/brevo";
// Reconcile uncertain send outcomes before retrying.const brevo = new BrevoClient({ apiKey: process.env.BREVO_API_KEY, maxRetries: 0 });
const result = await brevo.transactionalEmails.sendTransacEmail({ subject: "Order confirmed", htmlContent: "<html><body><p>Thanks for your order.</p></body></html>", tags: ["order-confirmation"],});
console.log("Message ID:", result.messageId);Le client Python s’installe avec pip install brevo-python. Si vous préférez ne pas embarquer une dépendance SDK pour deux endpoints, la surface HTTP brute est assez réduite pour être appelée directement, ce qui vous protège en prime des changements de version du SDK :
Le client Python s’installe avec pip install brevo-python. Pour quelques endpoints, vous pouvez aussi appeler HTTP directement :
import osimport requests
BASE = "https://api.brevo.com/v3"HEADERS = {"api-key": os.environ["BREVO_API_KEY"]}
def upsert_consented_contact(source_id, email, first_name, list_id, consent): if not consent: raise ValueError("No marketing consent in the source") if not source_id or not isinstance(list_id, int): raise ValueError("Stable source ID and numeric list ID required") # Configure FNAME in Brevo before using this explicit field mapping. response = requests.post( f"{BASE}/contacts", headers=HEADERS, json={ "email": email, "ext_id": source_id, "attributes": {"FNAME": first_name}, "listIds": [list_id], "updateEnabled": True, "getId": True, }, timeout=30, ) response.raise_for_status() return response.json().get("id") if response.content else NoneUne mise à jour réussie peut renvoyer un code 204 sans corps. None signifie alors qu’il faut relire le contact pour obtenir son identifiant.
Cet exemple suppose que l’appelant a déjà vérifié le consentement et les blocages dans Brevo. Il ne demande aucune réactivation. Avec une vraie clé, ce code écrit dans le compte ; testez-le d’abord avec des réponses simulées. Les conflits d’identifiants peuvent produire différents codes 4xx selon l’endpoint. Lisez la réponse et réconciliez les identifiants au lieu de présumer un code 409.
Webhooks
Les webhooks vous permettent de savoir ce qui s’est passé après un envoi. POST /v3/webhooks en crée un, avec url, events, type, et éventuellement channel (email ou sms), batched, des headers personnalisés et un objet auth.
Il existe trois types de webhooks, avec des vocabulaires d’événements distincts :
- Transactionnel :
sent,request,delivered,hardBounce,softBounce,blocked,spam,invalid,deferred,click,opened,uniqueOpened,unsubscribed. - Marketing :
spam,opened,click,hardBounce,softBounce,unsubscribed,listAddition,delivered,contactUpdated,contactDeleted. - Entrant :
inboundEmailProcessedetreply, qui exigent en plus undomain.
curl -X POST https://api.brevo.com/v3/webhooks \ -H "api-key: $BREVO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://yourapp.example/hooks/brevo", "type": "transactional", "events": ["delivered", "hardBounce", "spam", "unsubscribed"], "description": "Deliverability signals" }'Pour suivre les contacts, créez un webhook marketing avec contactUpdated ; la charge utile documentée nomme cet événement contact_updated. Authentifiez le destinataire avec le jeton bearer auth ou les en-têtes personnalisés du guide de sécurité Brevo. Vérifiez le secret avant de traiter l’événement et ne l’écrivez pas dans les journaux.
Conservez une clé de déduplication fondée sur le type d’événement, l’identifiant du contact et son horodatage pendant une période couvrant les reprises. L’horodatage seul n’est pas unique. Les livraisons peuvent être répétées ou désordonnées. Après un événement, relisez le contact et réconciliez son état avec votre source faisant autorité. Définissez une fenêtre de rejeu, écartez les événements trop anciens après une reprise et lancez régulièrement un contrôle incrémental. Un webhook signale un changement ; il ne réalise pas à lui seul une synchronisation bidirectionnelle. Le guide des connecteurs traite le choix global.
Limites de débit et gestion des erreurs
Les limites de débit de Brevo s’appliquent par endpoint et par niveau de forfait, et l’écart entre endpoints est énorme.
| Endpoint | General (tous les forfaits) | Advanced (Professional et Enterprise) |
|---|---|---|
POST /v3/smtp/email | 1 000 RPS | 2 000 RPS |
POST /v3/transactionalSMS/send | 150 RPS | 200 RPS |
/v3/contacts/... | 10 RPS, 36 000 RPH | 20 RPS, 72 000 RPH |
POST /v3/events | 10 RPS, 36 000 RPH | 20 RPS, 72 000 RPH |
GET /v3/smtp/emails | 2 RPS, 7 200 RPH | 3 RPS, 10 800 RPH |
| Endpoints sans limite propre | 100 RPH | 200 RPH |
Brevo documente x-sib-ratelimit-limit, x-sib-ratelimit-remaining et x-sib-ratelimit-reset pour les endpoints limités. Lisez-les aussi après un succès. Après un code 429, attendez la remise à zéro, puis ne réessayez que dans une limite fixe de tentatives et de délai. La valeur de reset est une durée dans l’unité de la fenêtre, généralement des secondes, pas un horodatage Unix.
async function callBrevo(path, init) { for (let attempt = 0; attempt < 3; attempt++) { const response = await fetch(`https://api.brevo.com/v3${path}`, { ...init, headers: { "api-key": process.env.BREVO_API_KEY, ...init.headers }, }); if (response.status !== 429 || attempt === 2) return response; const raw = Number(response.headers.get("x-sib-ratelimit-reset")); const seconds = Number.isFinite(raw) && raw > 0 ? raw : 2 ** attempt; const delayMs = (seconds + 2 ** attempt) * 1000; if (delayMs > 60_000) return response; await new Promise(resolve => setTimeout(resolve, delayMs)); }}Si l’attente nécessaire dépasse 60 secondes, la fonction renvoie 429. Reprenez plus tard avec une tâche planifiée.
Cet assistant ne convient qu’aux requêtes dont vous avez vérifié la possibilité de reprise. Après une erreur d’envoi ambiguë, réconciliez d’abord votre identifiant de message avant de renvoyer. Journalisez le statut et les en-têtes de débit, jamais la clé API ni les données de contact.
Ne relancez pas aveuglément une réponse 4xx. La référence de création de contact mentionne 400 et 425 ; un conflit d’identifiant n’est pas forcément 409. Après un code 5xx sur une écriture, vérifiez si le changement a déjà été appliqué.
Quel statut a été renvoyé ?
-
429
Attendre dans une limite fixe
Utilisez la durée de reset et arrêtez-vous après quelques tentatives.
-
5xx après une écriture
Vérifier son résultat
Le serveur peut avoir appliqué le changement avant l’erreur.
-
400
Corriger la requête
La requête est souvent invalide.
-
Conflit d’identifiants
Lire et réconcilier les ID
L’API ne promet pas de code de conflit précis.
Tester sans envoyer de courrier
Ajoutez l’en-tête X-Sib-Sandbox avec la valeur drop à un envoi transactionnel. Brevo valide la requête, renvoie un code 201 avec un messageId, ne livre rien et n’écrit aucun journal d’e-mail.
curl -X POST https://api.brevo.com/v3/smtp/email \ -H "api-key: $BREVO_API_KEY" \ -H "X-Sib-Sandbox: drop" \ -H "Content-Type: application/json" \ -d '{ "sender": { "email": "[email protected]" }, "to": [{ "email": "[email protected]" }], "subject": "Sandbox", "htmlContent": "<p>hi</p>" }'Comprenez bien ce que cela prouve et ce que cela ne prouve pas. Le mode bac à sable valide uniquement le format de la requête. Il ne dit rien de l’authentification de l’expéditeur, du rendu des modèles ni de la délivrabilité. Gardez un compte Brevo distinct pour les tests d’intégration de tout ce qui touche aux contacts ou aux données CRM, car le mode bac à sable couvre l’envoi et non le reste de l’API.
Avant de synchroniser des contacts
- Choisissez un identifiant source stable pour
ext_idet conservez l’identifiant Brevo renvoyé. - Vérifiez les attributs avant l’écriture. Brevo peut ignorer les champs inconnus ou du mauvais type ; relisez un contact de test.
- Utilisez une liste explicite et une preuve de consentement faisant autorité. Ignorez les blocages et désabonnements existants.
- Comptez séparément les enregistrements acceptés, appliqués, ignorés et en échec. Un code 202 pour un lot ou un objet ne confirme d’abord que l’acceptation.
- Traitez les webhooks marketing comme des signaux, puis réconciliez l’état actuel. Authentifiez, dédupliquez et bornez le rejeu.
- Surveillez les en-têtes de débit et arrêtez les reprises après un petit nombre d’essais.
Tajo se concentre sur la synchronisation des données commerciales avec Brevo. Si vous construisez cette couche vous-même, le guide des connecteurs Brevo compare les chemins et leurs limites.
Questions fréquemment posées
Quelle est l’URL de base de l’API Brevo ?
Tous les appels REST de Brevo passent par https://api.brevo.com/v3/. La version fait partie du chemin, et v3 est la génération actuelle. Un endpoint comme l’envoi transactionnel correspond donc au chemin complet https://api.brevo.com/v3/smtp/email.
Comment s’authentifier auprès de l’API Brevo ?
Envoyez votre clé dans un en-tête HTTP nommé api-key. Brevo n’utilise pas d’en-tête Authorization ni de préfixe Bearer pour les clés API standard. Les clés se génèrent dans les paramètres du compte, section SMTP et API, onglet clés API, et la valeur ne s’affiche qu’une seule fois.
Quelles sont les limites de débit de l’API Brevo ?
Les limites s’appliquent par endpoint et par palier de limite de débit. Sur le palier général, dont bénéficie chaque forfait, l’endpoint d’envoi transactionnel autorise 1 000 requêtes par seconde, les endpoints de contacts 10 par seconde, et les endpoints sans limite propre sont plafonnés à 100 requêtes par heure. Les forfaits Professional et Enterprise peuvent utiliser le palier Advanced, et Enterprise le palier Extended. Un dépassement renvoie un code 429.
Brevo propose-t-il des SDK officiels ?
Oui. Brevo publie des clients pour Node.js, Python, PHP, Java, C#, Go et Ruby sous l’organisation GitHub getbrevo. Le paquet Node s’appelle @getbrevo/brevo sur npm et le paquet Python brevo-python sur PyPI.
Comment tester l’API Brevo sans envoyer de vrais e-mails ?
Ajoutez l’en-tête X-Sib-Sandbox avec la valeur drop à un envoi transactionnel. Brevo valide la requête, renvoie un code 201 avec un messageId, n’envoie rien et n’écrit aucun journal d’e-mail. Cela vérifie uniquement le format de la requête, pas la délivrabilité.
Quels événements de webhook Brevo prend-il en charge ?
Les webhooks transactionnels couvrent notamment la livraison, les rebonds et les désabonnements. Les webhooks marketing comprennent contactUpdated et contactDeleted. Pour les événements entrants, Brevo documente inboundEmailProcessed.
Comment préserver un désabonnement lors d’une mise à jour de contact ?
Utilisez un identifiant source stable et des attributs déclarés. Ne définissez pas emailBlacklisted à false dans une mise à jour ordinaire. Vérifiez le contact existant et la source de consentement avant de l’ajouter à une liste marketing.
Un webhook suffit-il pour une synchronisation bidirectionnelle ?
Non. Authentifiez le destinataire, dédupliquez les livraisons et réconciliez les événements manqués ou désordonnés avec une source faisant autorité. Un webhook signale un changement, sans prouver que les deux systèmes concordent.