Integration guide

Implement the OAuth 2.0 authorization code flow in your application

This guide covers the HTTP contract for implementing OAuth in your own application — language-agnostic, with curl examples. If you want to start from working code, the Quickstart scaffolds a complete Node.js reference implementation.

Prerequisites

  • A registered Brevo OAuth app — create one with brevo app create or brevo app init
  • Your client_id, client_secret, and a registered redirect_uri

Get your credentials

$brevo app credentials --app-id <APP_ID> --reveal-secret
App name: my-app
App ID: e22eb778-a2a8-488a-a5e8-466b6dad9385
Client ID: 8768a0ad5801806c7946ca7c29648cc2
Client secret: <CLIENT_SECRET>
Scopes: all
Redirect URL 1: http://localhost:3009/auth/callback

Store client_secret on the server side only. Never expose it in client-side code, browser environments, or version control.

Step 1 — Redirect to Brevo

Send the user to the Brevo authorization endpoint. Your server constructs this URL and redirects the user’s browser to it.

https://oauth.brevo.com/realms/partner/oauth/authorize
?response_type=code
&client_id=<CLIENT_ID>
&redirect_uri=<REDIRECT_URI>
&scope=all
&state=<RANDOM_STATE>

Parameters:

ParameterRequiredDescription
response_typeYesMust be code
client_idYesYour app’s client ID
redirect_uriYesMust exactly match a URL registered on your app
scopeYesCurrently all. Granular scopes are coming soon.
stateRecommendedRandom string — verify in the callback to prevent CSRF attacks

Always generate a new random state value for each authorization request and verify it in the callback. Requests with a missing or mismatched state must be rejected.

The user sees the Brevo login page, authenticates with their Brevo credentials, and is redirected to your redirect_uri.

Step 2 — Handle the callback

After the user authenticates, Brevo redirects to your redirect_uri with:

https://your-app.com/callback?code=AUTH_CODE&state=YOUR_STATE

In your callback handler:

  1. Verify state matches the value you generated in Step 1 — reject mismatches
  2. Extract code — it expires in 10 minutes, exchange it immediately
  3. Proceed to Step 3

If the user denies access, the callback receives ?error=access_denied&error_description=... instead of ?code=.... Handle this case explicitly in your callback route.

Step 3 — Exchange code for tokens

POST the authorization code to the token endpoint along with your client credentials.

$curl --request POST \
> --url https://oauth.brevo.com/realms/partner/oauth/token \
> --header 'Content-Type: application/x-www-form-urlencoded' \
> --data-urlencode 'grant_type=authorization_code' \
> --data-urlencode 'client_id=<CLIENT_ID>' \
> --data-urlencode 'client_secret=<CLIENT_SECRET>' \
> --data-urlencode 'code=<AUTHORIZATION_CODE>' \
> --data-urlencode 'redirect_uri=<REDIRECT_URI>'

Response:

1{
2 "access_token": "eyJhbGci...",
3 "refresh_token": "eyJhbGci...",
4 "expires_in": 3600,
5 "token_type": "Bearer"
6}

Token fields:

FieldTypeDescription
access_tokenstringBearer token for API requests. Include in Authorization header.
refresh_tokenstringExchange for a new access token when the current one expires. Valid for 30 days.
expires_innumberAccess token lifetime in seconds — 3600 (1 hour).
token_typestringAlways Bearer.

Step 4 — Call the Brevo API

Include the access token as a Bearer token in the Authorization header on every API request.

$curl --request GET \
> --url https://api.brevo.com/v3/account \
> --header 'Accept: application/json' \
> --header 'Authorization: Bearer <ACCESS_TOKEN>'

Response:

1{
2 "email": "user@example.com",
3 "firstName": "Jane",
4 "lastName": "Smith",
5 "companyName": "Acme Corp"
6}

All Brevo API endpoints accept Bearer token authentication. See the API reference for available endpoints.

Step 5 — Refresh the access token

When the access token expires, use the refresh token to obtain a new one without requiring the user to re-authorize.

$curl --request POST \
> --url https://oauth.brevo.com/realms/partner/oauth/token \
> --header 'Content-Type: application/x-www-form-urlencoded' \
> --data-urlencode 'grant_type=refresh_token' \
> --data-urlencode 'client_id=<CLIENT_ID>' \
> --data-urlencode 'client_secret=<CLIENT_SECRET>' \
> --data-urlencode 'refresh_token=<REFRESH_TOKEN>'

The response has the same shape as Step 3. Always store the new access_token — and if a new refresh_token is returned, replace the stored one.

Security checklist

  • Never expose client_secret in client-side code or public repositories
  • Generate a unique state per request and validate it in the callback
  • Use HTTPS for all redirect_uri values in production
  • Store tokens server-side — not in localStorage or unprotected cookies
  • Always replace the stored refresh token when the server returns a new one
  • Never commit .env.local or files containing credentials