На этой странице
Создание контакта может вернуть 201, но результат всё равно окажется неверным: потерян ID источника, выбран другой список или проигнорирована отписка. Это решения о данных, а не мелочи HTTP.
Сначала показана работающая запись контакта, затем проверки для небольшой повторяемой синхронизации.
Что покрывает Brevo API
Всё живёт на одном хосте и в одном версионном пути. Документация для разработчиков делит поверхность на четыре продуктовые области:
- Сообщения: транзакционные письма, SMS и WhatsApp, включая пакетные отправки, планирование и активность по сообщениям.
- Маркетинговая платформа: контакты, списки, сегменты и email-кампании.
- Электронная коммерция: товары, заказы и отслеживание клиентских событий.
- Диалоги: чат-виджет и программное управление перепиской.
Эти области используют один аккаунт, одну базу контактов и один ключ API. Это удобно и иногда опасно: скрипт, написанный в расчёте на тестовые данные, обращается к тем же контактам, которым уходят ваши кампании.
Транзакционные письма против маркетинговых
Эти два семейства ведут себя достаточно по-разному, чтобы их путаница стала самой частой ошибкой проектирования.
| Транзакционные | Маркетинговые | |
|---|---|---|
| Основной эндпоинт | POST /v3/smtp/email | POST /v3/emailCampaigns |
| Адресация | Явные получатели в запросе | listIds или segmentIds |
| Триггер | Ваше приложение, в реальном времени | По расписанию или по требованию |
| Типичный профиль объёма | Непрерывный, по одному сообщению | Всплесками, одна большая рассылка |
| Позиция по лимитам | Очень высокая, 1 000 запросов в секунду на общем уровне | Низкая, эндпоинты кампаний попадают под общий лимит |
Если вы всё ещё решаете, подходит ли вам Brevo в принципе, эту тему закрывает обзор платформы.
Аутентификация и управление ключами
Brevo использует обычный ключ API в нестандартном заголовке. Заголовок называется api-key, а не Authorization, и префикса Bearer нет. На этом спотыкаются почти все, кто раньше работал с другим API для рассылок.
OAuth может использовать токен Bearer для поддерживаемых операций; обычный API-ключ по-прежнему передаётся в заголовке api-key.
curl https://api.brevo.com/v3/account \ -H "api-key: $BREVO_API_KEY"Ключи создаются в приложении Brevo в настройках аккаунта, в разделе SMTP and API, на вкладке API keys. Давайте каждому ключу понятное имя, привязанное к системе, которая им пользуется. Значение ключа показывается ровно один раз при создании, поэтому потерянный ключ не восстанавливают, а создают заново.
Несколько практических правил:
- Выпускайте отдельный ключ на каждую среду развёртывания и на каждый сервис. Отзыв скомпрометированного ключа не должен ронять три несвязанные системы.
- Обычные ключи API действуют на весь аккаунт. Считайте любой ключ полным доступом к контактам, отправке и данным CRM.
- Brevo также поддерживает OAuth 2.0 для приложений, действующих от имени других аккаунтов Brevo; этот сценарий описан рядом с ключами в разделе authentication schemes.
- Сервер MCP, которым пользуются AI-ассистенты, работает с отдельным токеном и действительно использует заголовок bearer. Этот токен создаётся на том же экране API keys, но не взаимозаменяем с ключом REST.
Базовый URL, версии и первая запись
Базовый URL: https://api.brevo.com/v3/. Версия указывается в пути, а не в заголовке, и v3 является текущим поколением. Все пути в этом руководстве отсчитываются от этой базы.
Для первой записи возьмите тестовый адрес под Вашим контролем, действующий ID списка и стабильный ID источника. Сначала создайте атрибут FNAME в Brevo. С настоящим ключом пример пишет напрямую:
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 }'Замените 42 на ID тестового списка. Перечитайте контакт и проверьте ID, имя, список и блокировки. Один ответ 2xx не подтверждает нужный результат.
Эндпоинты, которыми вы действительно будете пользоваться
Контакты
POST /v3/contacts создаёт контакт. Тело принимает email, объявленные attributes, listIds и ext_id как стабильный ключ источника. updateEnabled: true обновляет существующий контакт, а getId: true запрашивает итоговый ID. Brevo описывает эти поля. Передавайте тот же ext_id при каждом запуске и храните соответствие ID источника и Brevo.
До записи определите, какая система управляет каждым атрибутом и в какой список можно добавлять контакты с согласием. Указывайте listIds явно. Не отправляйте emailBlacklisted: false при обычном обновлении. Brevo предупреждает: смена адреса заблокированного контакта может снять блокировку. Обрабатывайте смену отдельно. Проверяйте блокировки и отписки перед добавлением в маркетинговый список. Между чтением и записью остаётся короткий интервал; если нужна строгая гарантия, проверьте ещё раз перед отправкой.
Чтение идёт через GET /v3/contacts, который постранично отдаёт данные по limit (по умолчанию 50, максимум 1000) и offset, а также поддерживает modifiedSince и createdSince в UTC. Инкрементальная синхронизация должна опираться на modifiedSince, а не обходить весь список. Учтите, что параметр filter поддерживает только оператор равенства, поэтому всё более выразительное относится к сегментам.
Для массовой загрузки POST /v3/contacts/import принимает fileUrl, fileBody или jsonBody, направляет данные в listIds и работает асинхронно, возвращая processId. Brevo документирует максимум 10 МБ на тело запроса и рекомендует держаться около 8 МБ, потому что разбор увеличивает полезную нагрузку. Указывайте notifyUrl, чтобы узнать результат, а не опрашивать статус.
Транзакционные письма
POST /v3/smtp/email является рабочей лошадкой. Помимо sender, to, subject и htmlContent, стоит знать такие поля:
templateIdвместе сparams, который заменяет встроенный контент шаблоном Brevo и подстановкой переменных. Параметры отдельной версии ограничены 100 КБ, суммарные параметры, 1000 КБ.messageVersions, который отправляет персонализированные варианты одним вызовом, до 99 получателей на версию.tagsмогут помочь связать события доставки с процессом приложения, который их вызвал.scheduledAtвместе сbatchIdдля будущих отправок, которые вы, возможно, захотите отменить группой.headersв формате Title-Case для пользовательских заголовков SMTP.
Один запрос принимает не более 2 000 получателей. О разнице между этим эндпоинтом и отправкой кампаний с точки зрения стратегии рассылок рассказывает руководство по транзакционным письмам.
Email-кампании
POST /v3/emailCampaigns требует name и sender, а также ровно один источник контента: htmlContent (минимум 10 символов, менее 1 МБ), htmlUrl или templateId. Аудитория указывается в recipients как listIds или segmentIds, а scheduledAt использует формат UTC YYYY-MM-DDTHH:mm:ss.SSSZ. Сопутствующие маршруты покрывают немедленную отправку, отправку теста, обновление статуса и получение отчёта по кампании.
Компании, сделки и объекты
В CRM от Brevo есть два пересекающихся пути записи, и правильный выбор здесь важен.
Маршруты CRM: POST /v3/companies, PATCH /v3/companies/{id}, DELETE /v3/companies/{id} и аналогичный набор для сделок. Они синхронные. PATCH возвращает 204 после применения изменения.
API объектов является массовым путём: POST /v3/objects/{object_type}/batch/upsert принимает до 1000 записей и 1 МБ на запрос, до 500 атрибутов на запись и до 10 связанных записей на тип объекта в каждой записи. Он возвращает 202 и processId, то есть «принято», а не «применено».
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" } } ] }'Руководство по Brevo CRM описывает модель объектов со стороны оператора.
Официальные SDK
Brevo поддерживает клиенты в организации getbrevo на GitHub:
| Язык | Репозиторий |
|---|---|
| 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 |
Клиент для Node устанавливается как @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);Клиент для Python ставится командой pip install brevo-python. Если вы предпочитаете не тащить зависимость SDK ради двух эндпоинтов, чистая HTTP-поверхность достаточно мала, чтобы обращаться к ней напрямую, что заодно защищает от смены версий SDK:
Python-клиент устанавливается командой pip install brevo-python. Для нескольких эндпоинтов подойдут прямые HTTP-запросы:
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 NoneУспешное обновление может вернуть пустой ответ 204. Тогда None означает, что для получения ID контакт надо прочитать снова.
Пример предполагает, что вызывающий код уже проверил согласие и блокировки Brevo. Разблокировки он не запрашивает. Настоящий ключ пишет в аккаунт; сначала протестируйте макеты ответов. Конфликт идентификаторов может вернуть разные 4xx. Прочитайте ответ и сверяйте ID, не ожидая всегда 409.
Вебхуки
Вебхуки нужны, чтобы узнавать, что произошло после отправки. POST /v3/webhooks создаёт вебхук с полями url, events, type, а также опционально channel (email или sms), batched, пользовательскими headers и объектом auth.
Есть три типа вебхуков с разными словарями событий:
- Транзакционные:
sent,request,delivered,hardBounce,softBounce,blocked,spam,invalid,deferred,click,opened,uniqueOpened,unsubscribed. - Маркетинговые:
spam,opened,click,hardBounce,softBounce,unsubscribed,listAddition,delivered,contactUpdated,contactDeleted. - Входящие:
inboundEmailProcessedиreply, которым дополнительно требуетсяdomain.
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" }'Для изменений контакта зарегистрируйте маркетинговый вебхук с contactUpdated. В данных Brevo называет событие contact_updated. Подтверждайте подлинность получателя через bearer-токен auth или собственные заголовки согласно руководству по защите. Проверяйте секрет до обработки и не пишите его в журнал.
Храните ключ удаления дублей из типа события, ID контакта и времени в течение окна повторной доставки. Одной временной метки недостаточно для уникальности. События повторяются и приходят не по порядку. После события перечитайте текущий контакт и сверьте с надёжным источником. Ограничьте окно воспроизведения, после восстановления отбрасывайте старые события, а пропуски закрывайте регулярной инкрементальной проверкой. Вебхук сам по себе не обеспечивает двустороннюю синхронизацию. См. руководство по коннекторам.
Лимиты запросов и обработка ошибок
Лимиты Brevo заданы отдельно для каждого эндпоинта и тарифа, и разброс между эндпоинтами огромен.
| Эндпоинт | General (каждый тариф) | Advanced (Professional и 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 |
| Эндпоинты без собственного лимита | 100 RPH | 200 RPH |
Brevo документирует x-sib-ratelimit-limit, x-sib-ratelimit-remaining и x-sib-ratelimit-reset для ограниченных эндпоинтов. Читайте их и при успехе. После 429 дождитесь сброса и повторяйте только в фиксированных пределах времени и попыток. Значение reset обозначает длительность в единицах окна, обычно секунды, а не 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)); }}Если требуется ждать больше 60 секунд, функция возвращает 429. Запланируйте следующую попытку позднее.
Функция подходит только для запросов, безопасное повторение которых Вы проверили. При неопределённой ошибке отправки сначала сверьте собственный ID сообщения. Записывайте статус и лимиты, а не ключ или данные контактов.
Не повторяйте все 4xx вслепую. Справка по созданию контакта перечисляет 400 и 425; конфликт не обязательно 409. После 5xx при записи проверьте, не была ли она выполнена.
Какой статус вернулся?
-
429
Ждите в пределах бюджета
Учитывайте время сброса и число попыток.
-
5xx после записи
Проверьте результат
Сервер мог записать данные до сбоя.
-
400
Исправьте запрос
Обычно входные данные неверны.
-
Конфликт ID
Сверьте идентификаторы
API не обещает единый код конфликта.
Тестирование без отправки писем
Добавьте к транзакционной отправке заголовок X-Sib-Sandbox со значением drop. Brevo проверит запрос, вернёт 201 с messageId, ничего не доставит и не запишет лог письма.
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>" }'Важно понимать, что это доказывает, а что нет. Режим песочницы проверяет только формат запроса. Он ничего не говорит об аутентификации отправителя, рендеринге шаблона или доставляемости. Держите отдельный аккаунт Brevo для интеграционного тестирования всего, что затрагивает контакты или данные CRM, потому что песочница покрывает отправку, но не остальную часть API.
Проверка перед синхронизацией контактов
- Выберите стабильный ID источника для
ext_idи сохраните ID Brevo. - Объявите атрибуты до записи и перечитайте тестовый контакт.
- Укажите конкретный список и подтверждённое согласие; пропускайте блокировки и отписки.
- Отдельно считайте принятые, применённые, пропущенные и неудачные записи. 202 сначала значит только приём.
- Проверяйте подлинность маркетинговых вебхуков, удаляйте дубли и сверяйте состояние.
- Следите за заголовками лимитов и ограничивайте повторы.
Tajo синхронизирует торговые данные с Brevo. При самостоятельной реализации см. руководство по коннекторам.
Часто Задаваемые Вопросы
Какой базовый URL у Brevo API?
Все REST-запросы к Brevo идут на https://api.brevo.com/v3/. Версия указывается в пути, и v3 является текущим поколением, поэтому, например, отправка транзакционного письма выполняется по полному пути https://api.brevo.com/v3/smtp/email.
Как пройти аутентификацию в Brevo API?
Передавайте ключ в HTTP-заголовке с именем api-key. Для обычных ключей Brevo не использует заголовок Authorization или схему Bearer. Ключи создаются в настройках аккаунта, в разделе SMTP and API, на вкладке API keys, и значение показывается только один раз.
Какие лимиты запросов действуют в Brevo API?
Лимиты задаются отдельно для каждого эндпоинта и каждого уровня лимитов. На общем уровне, который есть на каждом тарифе, эндпоинт транзакционной отправки допускает 1 000 запросов в секунду, эндпоинты контактов допускают 10 в секунду, а эндпоинты без собственного лимита ограничены 100 запросами в час. Тарифы Professional и Enterprise могут использовать уровень Advanced, а Enterprise также уровень Extended. При превышении лимита возвращается 429.
Есть ли у Brevo официальные SDK?
Да. Brevo публикует клиенты для Node.js, Python, PHP, Java, C#, Go и Ruby в организации getbrevo на GitHub. Пакет для Node называется @getbrevo/brevo в npm, пакет для Python, brevo-python в PyPI.
Как тестировать Brevo API, не отправляя реальные письма?
Добавьте к транзакционной отправке заголовок X-Sib-Sandbox со значением drop. Brevo проверит запрос, вернёт 201 с messageId, ничего не отправит и не запишет лог письма. Проверяется только формат запроса, а не доставляемость.
Какие события вебхуков поддерживает Brevo?
Транзакционные вебхуки сообщают о доставке, отказах и отписках. Маркетинговые включают contactUpdated и contactDeleted. Для входящих событий Brevo документирует inboundEmailProcessed.
Как обновление контакта сохраняет прежнюю отписку?
Используйте стабильный ID источника и объявленные атрибуты. При обычном обновлении не задавайте emailBlacklisted: false. Перед добавлением в маркетинговый список проверьте контакт и источник согласия.
Достаточно ли вебхука для двусторонней синхронизации?
Нет. Проверяйте подлинность получателя, удаляйте повторные доставки и сверяйте пропущенные или пришедшие не по порядку события с надёжным источником. Вебхук сообщает об изменении, но не доказывает совпадение состояния.