Op deze pagina
Een contactimport kan 201 teruggeven en toch fout zijn: de bron-ID ontbreekt, de verkeerde lijst is gekozen of een eerdere afmelding is genegeerd. Dat zijn keuzes over gegevensbeheer, geen HTTP-details.
Deze gids laat eerst een Brevo-verzoek werken en toont daarna de controles voor een herhaalbare kleine contactsynchronisatie.
Wat de Brevo API omvat
Alles staat onder één host en één versiepad. De ontwikkelaarsdocumentatie verdeelt dat oppervlak in vier productgebieden:
- Messaging: transactionele e-mail, SMS en WhatsApp, inclusief batchverzendingen, planning en berichtactiviteit.
- Marketingplatform: contacten, lijsten, segmenten en e-mailcampagnes.
- eCommerce: producten, bestellingen en het volgen van klantgebeurtenissen.
- Conversations: de chatwidget en programmatisch gespreksbeheer.
Die gebieden delen één account, één contactdatabase en één API-sleutel. Dat is handig en af en toe gevaarlijk: een script dat geschreven is vanuit een staging-idee van de data praat met dezelfde contacten waar je campagnes naartoe gaan.
Transactioneel versus marketing
De twee families gedragen zich verschillend genoeg dat ze door elkaar halen de meest gemaakte ontwerpfout is.
| Transactioneel | Marketing | |
|---|---|---|
| Belangrijkste endpoint | POST /v3/smtp/email | POST /v3/emailCampaigns |
| Adressering | Expliciete ontvangers in het verzoek | listIds of segmentIds |
| Trigger | Je applicatie, in realtime | Ingepland of op aanvraag verzonden |
| Typisch volumepatroon | Continu, één bericht tegelijk | Piekerig, één grote verzending |
| Houding rond rate limits | Zeer hoog, 1.000 verzoeken per seconde op de general-tier | Laag, campagne-endpoints vallen onder de algemene limiet |
Ben je nog aan het bepalen of Brevo überhaupt het juiste platform is, dan behandelt het platformoverzicht die vraag.
Authenticatie en sleutelbeheer
Brevo gebruikt een gewone API-sleutel in een eigen header. Die header heet api-key, niet Authorization, en er is geen Bearer-prefix. Daar struikelt bijna iedereen over die eerst een andere messaging-API heeft gebruikt.
OAuth kan voor ondersteunde bewerkingen een Bearer-token gebruiken; een gewone API-sleutel hoort in de header api-key.
curl https://api.brevo.com/v3/account \ -H "api-key: $BREVO_API_KEY"Sleutels maak je aan in de Brevo-app onder de accountinstellingen, in de sectie SMTP and API, op het tabblad API keys. Geef elke sleutel een beschrijvende naam die verwijst naar het systeem dat hem gebruikt. De waarde van de sleutel wordt precies één keer getoond bij het aanmaken, dus raak je hem kwijt, dan maak je een nieuwe aan in plaats van de oude terug te halen.
Een paar praktische regels:
- Geef een aparte sleutel uit per deployomgeving en per dienst. Het intrekken van een gelekte sleutel mag nooit drie ongerelateerde systemen platleggen.
- Standaard API-sleutels gelden accountbreed. Behandel elke sleutel als volledige toegang tot contacten, verzending en CRM-gegevens.
- Brevo ondersteunt ook OAuth 2.0 voor applicaties die namens andere Brevo-accounts handelen, beschreven naast de sleutelflow in authentication schemes.
- De MCP-server die AI-assistenten gebruiken werkt met een apart token en gebruikt wél een bearer-header. Dat token maak je aan in hetzelfde API keys-scherm, maar het is niet uitwisselbaar met een REST-sleutel.
Base URL, versies en je eerste schrijfactie
De base URL is https://api.brevo.com/v3/. De versie zit in het pad in plaats van in een header, en v3 is de huidige generatie. Elk pad in deze gids is relatief aan die basis.
Gebruik voor de eerste contactschrijfbewerking een testadres dat je beheert, een echte lijst-ID en een bron-ID die gelijk blijft. Maak eerst het attribuut FNAME in Brevo aan. Met een echte sleutel schrijft dit voorbeeld live:
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 }'Vervang 42 door de ID van je testlijst. Lees het contact terug en controleer ID, naam, lijst en blokkades. Alleen 2xx bewijst de gewenste synchronisatie niet.
De endpoints die je echt gaat gebruiken
Contacten
POST /v3/contacts maakt een contact aan. De inhoud accepteert email, gedeclareerde attributes, listIds en ext_id als stabiele broncode. Met updateEnabled: true werk je een bestaand contact bij. getId: true vraagt de overblijvende ID op. Brevo documenteert deze velden. Gebruik bij elke run dezelfde ext_id en bewaar de koppeling tussen bron-ID en Brevo-ID voor afstemming.
Bepaal vóór de eerste schrijfbewerking welk systeem elk attribuut beheert en welke lijst contacten met toestemming ontvangt. Geef listIds expliciet op. Zet emailBlacklisted: false nooit bij gewone updates. Brevo waarschuwt dat het wijzigen van een geblokkeerd e-mailadres de blokkade opheft. Behandel zo’n wijziging apart. Controleer blokkades en afmeldingen voordat je een contact aan een marketinglijst toevoegt. Tussen lezen en schrijven blijft een kort tijdvenster; voor strikte zekerheid is vlak vóór verzending nog een controle nodig.
Lezen gaat via GET /v3/contacts, dat pagineert met limit (standaard 50, maximaal 1000) en offset, en modifiedSince en createdSince in UTC ondersteunt. Incrementele synchronisaties leunen beter op modifiedSince dan op het aflopen van de volledige lijst. Let op: de parameter filter ondersteunt alleen een gelijkheidsoperator, dus alles wat expressiever is hoort in een segment thuis.
Voor bulkladen accepteert POST /v3/contacts/import een fileUrl, fileBody of jsonBody, richt zich op listIds en draait asynchroon, met een processId als resultaat. Brevo documenteert een maximum van 10 MB per body en raadt aan rond 8 MB te blijven, omdat het parsen de payload opblaast. Geef notifyUrl mee zodat je de uitkomst te horen krijgt in plaats van te moeten pollen.
Transactionele e-mail
POST /v3/smtp/email is het werkpaard. Naast sender, to, subject en htmlContent zijn dit de velden die je moet kennen:
templateIdmetparams, waarmee inline content wordt vervangen door een Brevo-template en de bijbehorende variabelen. Params per versie zijn begrensd op 100 KB, cumulatief op 1000 KB.messageVersions, waarmee je gepersonaliseerde varianten in één aanroep verstuurt, met maximaal 99 ontvangers per versie.tagskunnen helpen bezorggebeurtenissen te koppelen aan de toepassingsworkflow die ze veroorzaakte.scheduledAtplusbatchId, voor toekomstige verzendingen die je als groep wilt kunnen annuleren.headers, in Title-Case, voor eigen SMTP-headers.
Eén verzoek accepteert maximaal 2.000 ontvangers. Voor het verschil tussen dit endpoint en campagneverzending geeft de gids over transactionele e-mail de strategische kant.
E-mailcampagnes
POST /v3/emailCampaigns vereist name en sender, plus precies één contentbron: htmlContent (minimaal 10 tekens, onder 1 MB), htmlUrl of templateId. De doelgroep gaat in recipients als listIds of segmentIds, en scheduledAt gebruikt het UTC-formaat YYYY-MM-DDTHH:mm:ss.SSSZ. Bijbehorende routes dekken direct verzenden, een test versturen, de status bijwerken en het campagnerapport ophalen.
Bedrijven, deals en objecten
Het CRM van Brevo heeft twee overlappende schrijfpaden, en het juiste kiezen doet ertoe.
De CRM-routes zijn POST /v3/companies, PATCH /v3/companies/{id}, DELETE /v3/companies/{id} en de vergelijkbare set voor deals. Die zijn synchroon. Een PATCH geeft 204 terug zodra de wijziging is doorgevoerd.
De objects-API is het bulkpad: POST /v3/objects/{object_type}/batch/upsert neemt tot 1000 records en 1 MB per verzoek, tot 500 attributen per record en tot 10 associatierecords per objecttype per record. Hij geeft 202 terug met een processId, wat geaccepteerd betekent en niet doorgevoerd.
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" } } ] }'De Brevo CRM-gids behandelt het objectmodel vanuit de kant van de operator.
Officiële SDK’s
Brevo onderhoudt clients onder de GitHub-organisatie getbrevo:
| Taal | Repository |
|---|---|
| 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 |
De Node-client installeer je als @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);De Python-client installeer je met pip install brevo-python. Wil je liever geen SDK-afhankelijkheid meeslepen voor twee endpoints, dan is het ruwe HTTP-oppervlak klein genoeg om direct aan te roepen, wat je bovendien beschermt tegen versiegedoe in de SDK:
De Python-client installeer je met pip install brevo-python. Voor een paar eindpunten kun je HTTP ook direct aanroepen:
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 NoneEen geslaagde update kan een leeg 204-antwoord geven. None betekent dan dat je het contact opnieuw moet lezen voor zijn ID.
Dit voorbeeld gaat ervan uit dat de aanroeper toestemming en Brevo-blokkades al heeft gecontroleerd. Het vraagt geen deblokkering. Met een echte sleutel schrijft de code in het account; test eerst met mocks. Identiteitsconflicten kunnen verschillende 4xx-antwoorden geven. Lees de reactie en stem ID’s af, zonder altijd 409 te verwachten.
Webhooks
Webhooks zijn hoe je te weten komt wat er na een verzending gebeurt. POST /v3/webhooks maakt er een aan, met url, events, type en optioneel channel (email of sms), batched, eigen headers en een auth-object.
Er zijn drie webhooktypes met elk een eigen gebeurtenissenwoordenboek:
- Transactional:
sent,request,delivered,hardBounce,softBounce,blocked,spam,invalid,deferred,click,opened,uniqueOpened,unsubscribed. - Marketing:
spam,opened,click,hardBounce,softBounce,unsubscribed,listAddition,delivered,contactUpdated,contactDeleted. - Inbound:
inboundEmailProcessed, waarvoor eendomainnodig is.
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" }'Registreer voor contactwijzigingen een marketing-webhook met contactUpdated. De Brevo-documentatie noemt de gebeurtenis in het bericht contact_updated. Verifieer de ontvanger met het bearer-token in auth of aangepaste headers uit de beveiligingsgids. Controleer het geheim voordat je het bericht verwerkt en log het niet.
Bewaar voor deduplicatie een sleutel met gebeurtenistype, contact-ID en tijdstempel zolang als nieuwe afleveringen mogelijk zijn. Een tijdstempel alleen is niet uniek. Berichten kunnen dubbel of in een andere volgorde binnenkomen. Lees na een gebeurtenis het huidige contact en stem het af met je gezaghebbende bron. Definieer een replayvenster, weiger oude berichten na herstel en voer regelmatig een incrementele controle uit. Een webhook meldt een verandering; hij maakt op zichzelf geen tweerichtingssynchronisatie. De connectorgids bespreekt de keuze.
Rate limits en foutafhandeling
De rate limits van Brevo gelden per endpoint en per abonnementsniveau, en de spreiding tussen endpoints is enorm.
| Endpoint | General (elk abonnement) | Advanced (Professional en 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 zonder eigen limiet | 100 RPH | 200 RPH |
Brevo documenteert x-sib-ratelimit-limit, x-sib-ratelimit-remaining en x-sib-ratelimit-reset voor begrensde eindpunten. Lees ze ook bij geslaagde antwoorden. Wacht bij 429 tot de reset en probeer alleen opnieuw binnen een vaste grens voor pogingen en wachttijd. De resetwaarde is een duur in de eenheid van het venster, meestal seconden, geen Unix-tijdstempel.
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)); }}Als de vereiste wachttijd meer dan 60 seconden is, geeft de functie 429 terug. Plan een latere poging.
Deze hulpfunctie is alleen voor verzoeken waarvan je hebt gecontroleerd dat ze veilig herhaalbaar zijn. Stem bij een onduidelijke verzendfout eerst je bericht-ID af voordat je nogmaals verzendt. Log status en limietheaders, maar geen API-sleutel of contactgegevens.
Probeer een 4xx niet blind opnieuw. De contactreferentie noemt 400 en 425; een identiteitsconflict hoeft geen 409 op te leveren. Controleer na een 5xx bij schrijven of de wijziging al is doorgevoerd.
Welke status kwam terug?
-
429
Wacht binnen een grens
Gebruik de resetduur en stop na enkele pogingen.
-
5xx na schrijven
Controleer of het is gelukt
De server kan vóór de fout hebben geschreven.
-
400
Herstel het verzoek
Het verzoek is vaak onjuist.
-
Identiteitsconflict
Lees ID’s en stem ze af
De API belooft geen specifieke conflictstatus.
Testen zonder e-mail te versturen
Voeg de header X-Sib-Sandbox met de waarde drop toe aan een transactionele verzending. Brevo valideert het verzoek, geeft 201 terug met een messageId, bezorgt niets en schrijft geen e-maillog weg.
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>" }'Begrijp goed wat dit wel en niet bewijst. Sandboxmodus valideert alleen het formaat van het verzoek. Het zegt niets over afzenderauthenticatie, templaterendering of bezorgbaarheid. Houd een apart Brevo-account aan voor integratietests van alles wat contacten of CRM-gegevens raakt, want sandboxmodus dekt verzending en niet de rest van de API.
Voor je contacten synchroniseert
- Kies een stabiele bron-ID voor
ext_iden bewaar de teruggegeven Brevo-ID. - Controleer attributen vooraf. Brevo kan onbekende of verkeerd getypte velden negeren; lees een testcontact terug.
- Gebruik een expliciete lijst en een gezaghebbend toestemmingsbewijs. Sla geblokkeerde en afgemelde contacten over.
- Tel aangenomen, toegepaste, overgeslagen en mislukte records apart. Een 202 voor batches of objecten bevestigt eerst alleen acceptatie.
- Gebruik marketingwebhooks als signalen en stem daarna de huidige staat af. Verifieer, dedupliceer en begrens replay.
- Bekijk de limietheaders en stop na enkele pogingen.
Tajo richt zich op het synchroniseren van commerciële gegevens met Brevo. Bouw je deze laag zelf, dan vergelijkt de Brevo-connectorgids routes en fouten.
Veelgestelde vragen
Wat is de base URL van de Brevo API?
Alle Brevo REST-aanroepen gaan naar https://api.brevo.com/v3/. De versie zit in het pad en v3 is de huidige generatie, dus een endpoint zoals de transactionele verzending heeft het volledige pad https://api.brevo.com/v3/smtp/email.
Hoe authenticeer je met de Brevo API?
Stuur je sleutel mee in een HTTP-header met de naam api-key. Brevo gebruikt geen Authorization- of Bearer-header voor standaard API-sleutels. Sleutels maak je aan onder de accountinstellingen, bij SMTP and API, tabblad API keys, en de waarde wordt maar één keer getoond.
Wat zijn de rate limits van de Brevo API?
De limieten gelden per endpoint en per rate limit-tier. Op de general-tier, die elk abonnement krijgt, staat het transactionele verzendendpoint 1.000 verzoeken per seconde toe, de contactendpoints 10 per seconde, en endpoints zonder eigen limiet zitten op maximaal 100 verzoeken per uur. Professional- en Enterprise-abonnementen kunnen de advanced-tier gebruiken, en Enterprise de extended-tier. Ga je over een limiet heen, dan volgt een 429.
Heeft Brevo officiële SDK’s?
Ja. Brevo publiceert clients voor Node.js, Python, PHP, Java, C#, Go en Ruby onder de GitHub-organisatie getbrevo. Het Node-pakket heet @getbrevo/brevo op npm en het Python-pakket brevo-python op PyPI.
Hoe test ik de Brevo API zonder echte e-mail te versturen?
Voeg de header X-Sib-Sandbox met de waarde drop toe aan een transactionele verzending. Brevo valideert het verzoek, geeft 201 met een messageId terug, verstuurt niets en schrijft geen e-maillog weg. Het controleert alleen het formaat van het verzoek, niet de bezorgbaarheid.
Welke webhookgebeurtenissen ondersteunt Brevo?
Transactionele webhooks melden onder meer levering, bounces en afmeldingen. Marketingwebhooks bevatten contactUpdated en contactDeleted. Voor inkomende gebeurtenissen documenteert Brevo inboundEmailProcessed.
Hoe beschermt een contactupdate een eerdere afmelding?
Gebruik een stabiele bron-ID en gedeclareerde attributen. Zet emailBlacklisted bij gewone updates niet op false. Controleer het bestaande contact en je toestemmingsbron voordat je het aan een marketinglijst toevoegt.
Is een webhook genoeg voor tweerichtingssynchronisatie?
Nee. Verifieer de ontvanger, verwijder dubbele afleveringen en stem gemiste of ongeordende gebeurtenissen af met een gezaghebbende bron. Een webhook is een veranderingssignaal, geen bewijs dat beide systemen gelijklopen.