En esta página
Una importación de contactos puede devolver 201 y seguir estando mal: se perdió el ID de origen, se eligió la lista equivocada o se ignoró una baja anterior. Son decisiones sobre quién gobierna los datos, no detalles HTTP.
Esta guía muestra primero cómo hacer una solicitud a Brevo y luego las comprobaciones que vuelven repetible una pequeña sincronización.
Qué cubre la API de Brevo
Todo vive bajo un único host y una única ruta de versión. La documentación para desarrolladores agrupa la superficie en cuatro áreas de producto:
- Mensajería: email transaccional, SMS y WhatsApp, incluidos los envíos por lotes, la programación y la actividad de los mensajes.
- Plataforma de marketing: contactos, listas, segmentos y campañas de email.
- eCommerce: productos, pedidos y seguimiento de eventos de cliente.
- Conversaciones: el widget de chat y la gestión programática de conversaciones.
Esas áreas comparten una cuenta, una base de datos de contactos y una clave de API. Eso es cómodo y de vez en cuando peligroso: un script escrito pensando en datos de staging está hablando con los mismos contactos a los que envían tus campañas.
Transaccional frente a marketing
Las dos familias se comportan de forma lo bastante distinta como para que confundirlas sea el error de diseño más habitual.
| Transaccional | Marketing | |
|---|---|---|
| Endpoint principal | POST /v3/smtp/email | POST /v3/emailCampaigns |
| Destinatarios | Explícitos en la solicitud | listIds o segmentIds |
| Disparador | Tu aplicación, en tiempo real | Programado o enviado a demanda |
| Forma habitual del volumen | Continuo, un mensaje cada vez | A ráfagas, un envío grande |
| Postura ante los límites de tasa | Muy alta, 1.000 solicitudes por segundo en el nivel general | Baja, los endpoints de campaña caen bajo el límite general |
Si todavía estás decidiendo si Brevo es la plataforma adecuada, la visión general de la plataforma cubre ese terreno.
Autenticación y gestión de claves
Para una clave de API estándar, Brevo usa la cabecera api-key; algunas operaciones también aceptan tokens OAuth bearer. La cabecera se llama api-key, no Authorization, y no lleva prefijo Bearer. Esto despista a casi todo el mundo que ha usado antes otra API de mensajería.
OAuth puede usar un token Bearer en operaciones compatibles; una clave API estándar se envía en api-key.
curl https://api.brevo.com/v3/account \ -H "api-key: $BREVO_API_KEY"Las claves se generan en la aplicación de Brevo, dentro de los ajustes de la cuenta, en la sección SMTP y API, en la pestaña de claves de API. Dale a cada clave un nombre descriptivo ligado al sistema que la usa. El valor de la clave se muestra exactamente una vez cuando se genera, así que si la pierdes generas una nueva en lugar de recuperar la antigua.
Unas cuantas reglas prácticas:
- Emite una clave separada por entorno de despliegue y por servicio. Revocar una clave comprometida nunca debería tumbar tres sistemas que no tienen nada que ver.
- Las claves de API estándar son de ámbito de cuenta. Trata cualquier clave como acceso total a contactos, envíos y datos de CRM.
- Brevo también admite OAuth 2.0 para aplicaciones que actúan en nombre de otras cuentas de Brevo, descrito junto al flujo de claves en authentication schemes.
- El servidor MCP que usan los asistentes de AI toma un token aparte y ese sí usa una cabecera bearer. Ese token se genera en la misma pantalla de claves de API, pero no es intercambiable con una clave REST.
URL base, versionado y tu primera escritura
La URL base es https://api.brevo.com/v3/. La versión va en la ruta y no en una cabecera, y v3 es la generación actual. Todas las rutas de esta guía son relativas a esa base.
Para la primera escritura de contacto, usa una dirección de prueba bajo tu control, un ID de lista real y un ID de origen que no cambie. Crea antes el atributo FNAME en Brevo. Con una clave real, este ejemplo escribe en la cuenta:
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 }'Sustituye 42 por el ID de tu lista de prueba. Vuelve a leer el contacto y comprueba ID, nombre, lista y bloqueos. Un 2xx por sí solo no prueba que la sincronización sea correcta.
Los endpoints que vas a usar de verdad
Contactos
POST /v3/contacts crea un contacto. El cuerpo admite email, attributes declarados, listIds y ext_id como clave de origen estable. updateEnabled: true actualiza un contacto existente y getId: true pide el ID resultante. Brevo documenta estos campos. Reutiliza el mismo ext_id en cada ejecución y conserva la correspondencia entre IDs de origen y de Brevo para reconciliar cambios.
Antes de escribir, decide qué sistema gobierna cada atributo y qué lista recibe a los contactos con consentimiento. Indica listIds de forma explícita. Nunca establezcas emailBlacklisted: false en una actualización rutinaria. Brevo advierte que cambiar el correo de un contacto bloqueado elimina el bloqueo. Trata ese cambio por separado. Comprueba bloqueos y bajas antes de añadirlo a una lista de marketing. Sigue existiendo una carrera entre lectura y escritura; si necesitas una garantía estricta, haz una última comprobación antes de enviar.
Las lecturas pasan por GET /v3/contacts, que pagina con limit (50 por defecto, 1000 como máximo) y offset, y admite modifiedSince y createdSince en UTC. Las sincronizaciones incrementales deberían apoyarse en modifiedSince en lugar de recorrer la lista completa. Ten en cuenta que el parámetro filter solo admite un operador de igualdad, así que cualquier cosa más expresiva pertenece a un segmento.
Para carga masiva, POST /v3/contacts/import acepta fileUrl, fileBody o jsonBody, apunta a listIds y se ejecuta de forma asíncrona devolviendo un processId. Brevo documenta un máximo de 10 MB por cuerpo y recomienda quedarse cerca de 8 MB porque el parseo infla la carga útil. Facilita notifyUrl para enterarte del resultado en lugar de hacer polling.
Email transaccional
POST /v3/smtp/email es el caballo de batalla. Más allá de sender, to, subject y htmlContent, los campos que merece la pena conocer son:
templateIdconparams, que sustituye el contenido en línea por una plantilla de Brevo y sus sustituciones de variables. Los params de cada versión están limitados a 100 KB y los acumulados a 1000 KB.messageVersions, que envía variantes personalizadas en una sola llamada, con hasta 99 destinatarios por versión.tagspueden ayudar a relacionar los eventos de entrega con el flujo de la aplicación que los produjo.scheduledAtjunto conbatchId, para envíos futuros que quizá quieras cancelar en grupo.headers, en Title-Case, para cabeceras SMTP personalizadas.
Una sola solicitud acepta como máximo 2.000 destinatarios. Para la diferencia entre este endpoint y el envío de campañas, la guía de email transaccional ofrece la visión de estrategia de mensajería.
Campañas de email
POST /v3/emailCampaigns requiere name y sender, además de exactamente una fuente de contenido: htmlContent (mínimo 10 caracteres, menos de 1 MB), htmlUrl o templateId. La audiencia va en recipients como listIds o segmentIds, y scheduledAt usa el formato UTC YYYY-MM-DDTHH:mm:ss.SSSZ. Las rutas complementarias cubren el envío inmediato, el envío de una prueba, la actualización del estado y la descarga del informe de campaña.
Empresas, negocios y objetos
El CRM de Brevo tiene dos rutas de escritura solapadas, y elegir bien importa.
Las rutas de CRM son POST /v3/companies, PATCH /v3/companies/{id}, DELETE /v3/companies/{id} y el conjunto equivalente para los negocios. Son síncronas. Un PATCH devuelve 204 en cuanto se aplica el cambio.
La API de objetos es la vía masiva: POST /v3/objects/{object_type}/batch/upsert acepta hasta 1000 registros y 1 MB por solicitud, hasta 500 atributos por registro y hasta 10 registros de asociación por tipo de objeto y registro. Devuelve 202 con un processId, lo que significa aceptado y no aplicado.
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" } } ] }'La guía de Brevo CRM cubre el modelo de objetos desde el lado del operador.
SDK oficiales
Brevo mantiene clientes en la organización de GitHub getbrevo:
| Lenguaje | Repositorio |
|---|---|
| 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 |
El cliente de Node se instala como @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);El cliente de Python se instala con pip install brevo-python. Si prefieres no arrastrar una dependencia de SDK para dos endpoints, la superficie HTTP en crudo es lo bastante pequeña como para llamarla directamente, lo que además te aísla de los cambios de versión del SDK:
El cliente de Python se instala con pip install brevo-python. Para unos pocos endpoints también puedes llamar a HTTP directamente:
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 NoneUna actualización correcta puede devolver 204 sin cuerpo. En ese caso, None indica que debes volver a leer el contacto para obtener su ID.
Este ejemplo supone que el código que llama ya ha comprobado el consentimiento y los bloqueos en Brevo. No solicita quitar ningún bloqueo. Con una clave real, el código escribe en la cuenta; pruébalo antes con respuestas simuladas. Los conflictos de identidad pueden devolver distintos códigos 4xx según el endpoint. Lee la respuesta y reconcilia los IDs en vez de suponer siempre un 409.
Webhooks
Los webhooks son la forma de enterarte de lo que pasó después de un envío. POST /v3/webhooks crea uno, con url, events, type y, de forma opcional, channel (email o sms), batched, headers personalizadas y un objeto auth.
Hay tres tipos de webhook con vocabularios de evento distintos:
- Transaccional:
sent,request,delivered,hardBounce,softBounce,blocked,spam,invalid,deferred,click,opened,uniqueOpened,unsubscribed. - Marketing:
spam,opened,click,hardBounce,softBounce,unsubscribed,listAddition,delivered,contactUpdated,contactDeleted. - Entrante:
inboundEmailProcessedyreply, que además requieren 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" }'Para cambios de contactos, registra un webhook de marketing con contactUpdated. La carga útil documentada llama al evento contact_updated. Autentica el receptor con el token bearer de auth o las cabeceras personalizadas del manual de seguridad de Brevo. Valida el secreto antes de procesar el evento y no lo registres.
Guarda una clave de deduplicación formada por tipo de evento, identificador de contacto y marca temporal durante un periodo suficiente para cubrir reintentos. La marca temporal sola no es única. Las entregas pueden repetirse o llegar fuera de orden. Tras cada evento, consulta el contacto actual y reconcilia el estado con tu fuente autorizada. Define una ventana de reproducción, rechaza eventos antiguos al recuperarte y ejecuta una revisión incremental periódica. Un webhook señala un cambio; por sí solo no crea una sincronización bidireccional. La guía de conectores explica las opciones.
Límites de tasa y gestión de errores
Los límites de tasa de Brevo son por endpoint y por nivel de plan, y la diferencia entre endpoints es enorme.
| Endpoint | General (todos los planes) | Advanced (Professional y 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 sin límite propio | 100 RPH | 200 RPH |
Brevo documenta x-sib-ratelimit-limit, x-sib-ratelimit-remaining y x-sib-ratelimit-reset para los endpoints limitados. Léelas también tras un éxito. Ante un 429, espera al reinicio y reintenta dentro de un presupuesto fijo de intentos y demora. El valor de reset es una duración en la unidad de la ventana, normalmente segundos, no una marca de tiempo 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 la espera necesaria supera 60 segundos, la función devuelve 429. Programa otro intento para más tarde.
Este auxiliar solo sirve para solicitudes cuya repetición hayas comprobado que es segura. Tras un fallo de envío ambiguo, reconcilia primero tu identificador de mensaje antes de volver a enviar. Registra el estado y las cabeceras de límite, nunca la clave API ni los datos del contacto.
No repitas un 4xx a ciegas. La referencia de creación de contactos enumera 400 y 425; un conflicto de identidad no tiene por qué devolver 409. Tras un 5xx en una escritura, comprueba si el cambio ya se aplicó.
¿Qué estado devolvió?
-
429
Espera con un límite de reintentos
Usa la duración de reset y detente tras unos pocos intentos.
-
5xx después de escribir
Comprueba si se aplicó
El servidor puede haber aplicado el cambio antes del error.
-
400
Corrige la solicitud
Normalmente la solicitud es incorrecta.
-
Conflicto de identidad
Lee y reconcilia los IDs
La API no promete un código de conflicto concreto.
Probar sin enviar correo
Añade la cabecera X-Sib-Sandbox con el valor drop a un envío transaccional. Brevo valida la solicitud, devuelve 201 con un messageId, no entrega nada y no escribe ningún registro de email.
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>" }'Entiende qué demuestra esto y qué no. El modo sandbox valida solo el formato de la solicitud. No dice nada sobre la autenticación del remitente, el renderizado de plantillas ni la entregabilidad. Mantén una cuenta de Brevo aparte para las pruebas de integración de todo lo que toque contactos o datos de CRM, porque el modo sandbox cubre el envío y no el resto de la API.
Antes de sincronizar contactos
- Elige un ID de origen estable para
ext_idy conserva el ID de Brevo devuelto. - Verifica los atributos antes de escribir. Brevo puede ignorar campos desconocidos o con tipos erróneos; vuelve a leer un contacto de prueba.
- Usa un ID de lista explícito y un registro de consentimiento autorizado. Omite bloqueos y bajas existentes.
- Cuenta por separado registros aceptados, aplicados, omitidos y fallidos. Un 202 en lotes u objetos solo confirma su aceptación inicial.
- Usa los webhooks de marketing como señales y reconcilia luego el estado actual. Autentica, deduplica y limita la reproducción.
- Observa las cabeceras de límite y detente tras pocos reintentos.
Tajo se centra en sincronizar los datos comerciales con Brevo. Si construyes esta capa, la guía de conectores de Brevo compara los caminos y sus fallos.
Preguntas frecuentes
¿Cuál es la URL base de la API de Brevo?
Todas las llamadas REST de Brevo van a https://api.brevo.com/v3/. La versión forma parte de la ruta y v3 es la generación actual, así que un endpoint como el envío transaccional es la ruta completa https://api.brevo.com/v3/smtp/email.
¿Cómo te autenticas en la API de Brevo?
Envía tu clave en una cabecera HTTP llamada api-key. Brevo no usa una cabecera Authorization ni un prefijo Bearer para las claves de API estándar. Las claves se generan en los ajustes de la cuenta, en la sección SMTP y API, y el valor se muestra una sola vez.
¿Cuáles son los límites de tasa de la API de Brevo?
Los límites son por endpoint y por nivel de límite de tasa. En el nivel general, que tienen todos los planes, el endpoint de envío transaccional permite 1.000 solicitudes por segundo, los endpoints de contactos permiten 10 por segundo y los endpoints sin límite propio están limitados a 100 solicitudes por hora. Los planes Professional y Enterprise pueden usar el nivel Advanced, y Enterprise el nivel Extended. Superar un límite devuelve 429.
¿Brevo tiene SDK oficiales?
Sí. Brevo publica clientes para Node.js, Python, PHP, Java, C#, Go y Ruby en la organización de GitHub getbrevo. El paquete de Node es @getbrevo/brevo en npm y el de Python es brevo-python en PyPI.
¿Cómo pruebo la API de Brevo sin enviar correos reales?
Añade la cabecera X-Sib-Sandbox con el valor drop a un envío transaccional. Brevo valida la solicitud, devuelve 201 con un messageId, no envía nada y no escribe ningún registro de email. Solo comprueba el formato de la solicitud, no la entregabilidad.
¿Qué eventos de webhook admite Brevo?
Los webhooks transaccionales cubren, entre otros, entregas, rebotes y bajas. Los de marketing incluyen contactUpdated y contactDeleted. Para eventos entrantes, Brevo documenta inboundEmailProcessed.
¿Cómo protege una actualización de contacto una baja previa?
Usa un ID de origen estable y atributos declarados. No establezcas emailBlacklisted en false en actualizaciones normales. Comprueba el contacto existente y tu fuente de consentimiento antes de añadirlo a una lista de marketing.
¿Basta un webhook para una sincronización bidireccional?
No. Autentica el receptor, elimina entregas duplicadas y reconcilia los eventos perdidos o desordenados con una fuente autorizada. Un webhook señala un cambio, pero no prueba que ambos sistemas coincidan.