Introduction
API REST publique d'eBurnieSend — envoi de notifications WhatsApp / SMS / Email pour le marché ouest-africain.
This documentation aims to provide all the information you need to work with our API.
<aside>As you scroll, you'll see code examples for working with the API in different programming languages in the dark area to the right (or as part of the content on mobile).
You can switch the language used with the tabs at the top right (or from the nav menu at the top left on mobile).</aside>
Authenticating requests
To authenticate requests, include an Authorization header with the value "Bearer nh_live_xxx".
All authenticated endpoints are marked with a requires authentication badge in the documentation below.
Génère ta clé API depuis le dashboard ou via tinker (cf. README).
Contacts
Lister les contacts
requires authentication
Retourne le carnet d'adresses du tenant. Pagination cursor (max 200/page). Filtres facultatifs sur téléphone E.164, email, external_id (votre identifiant client) ou recherche libre.
Example request:
curl --request GET \
--get "https://eburniesend.com/api/v1/contacts?per_page=50&phone_e164=%2B22505XXXXXXXX&email=client%40example.com&external_id=CUST-1234&q=moustapha" \
--header "Authorization: Bearer nh_live_xxx" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://eburniesend.com/api/v1/contacts"
);
const params = {
"per_page": "50",
"phone_e164": "+22505XXXXXXXX",
"email": "[email protected]",
"external_id": "CUST-1234",
"q": "moustapha",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer nh_live_xxx",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, success):
{
"data": [],
"meta": {
"current_page": 1,
"per_page": 50,
"total": 0
}
}
Example response (401, unauthorized):
{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key",
"details": null
}
}
Example response (429, rate_limited):
{
"error": {
"code": "rate_limit_exceeded",
"message": "Too many requests",
"details": {
"retry_after": 60
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Créer un contact
requires authentication
Crée un nouveau contact dans le carnet d'adresses du tenant. Au
moins un identifiant (phone_e164 OU email) est requis. Les
opt-ins par canal sont initialisés à false par défaut.
Example request:
curl --request POST \
"https://eburniesend.com/api/v1/contacts" \
--header "Authorization: Bearer nh_live_xxx" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"phone_e164\": \"+22505XXXXXXXX\",
\"email\": \"[email protected]\",
\"first_name\": \"Jean\",
\"last_name\": \"Diallo\",
\"external_id\": \"CUST-1234\",
\"language\": \"fr\",
\"country_code\": \"CI\",
\"opt_in_whatsapp\": true,
\"opt_in_sms\": true,
\"opt_in_email\": true,
\"tags\": [
\"vip\",
\"newsletter\"
],
\"custom_attributes\": {
\"age\": 42,
\"city\": \"Abidjan\"
}
}"
const url = new URL(
"https://eburniesend.com/api/v1/contacts"
);
const headers = {
"Authorization": "Bearer nh_live_xxx",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"phone_e164": "+22505XXXXXXXX",
"email": "[email protected]",
"first_name": "Jean",
"last_name": "Diallo",
"external_id": "CUST-1234",
"language": "fr",
"country_code": "CI",
"opt_in_whatsapp": true,
"opt_in_sms": true,
"opt_in_email": true,
"tags": [
"vip",
"newsletter"
],
"custom_attributes": {
"age": 42,
"city": "Abidjan"
}
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201, created):
{
"data": {
"id": "7c8f9d10-1234-4567-89ab-cdef01234567",
"phone_e164": "+22505XXXXXXXX",
"email": "[email protected]"
}
}
Example response (401, unauthorized):
{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key",
"details": null
}
}
Example response (422, validation_failed):
{
"error": {
"code": "validation_failed",
"message": "The given data was invalid.",
"details": {
"fields": {
"phone_e164": [
"The phone_e164 field is required when email is not present."
]
}
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Détails d'un contact
requires authentication
Example request:
curl --request GET \
--get "https://eburniesend.com/api/v1/contacts/7c8f9d10-1234-4567-89ab-cdef01234567" \
--header "Authorization: Bearer nh_live_xxx" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://eburniesend.com/api/v1/contacts/7c8f9d10-1234-4567-89ab-cdef01234567"
);
const headers = {
"Authorization": "Bearer nh_live_xxx",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, success):
{
"data": {
"id": "7c8f9d10-1234-4567-89ab-cdef01234567",
"phone_e164": "+22505XXXXXXXX",
"email": "[email protected]",
"first_name": "Jean"
}
}
Example response (401, unauthorized):
{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key",
"details": null
}
}
Example response (404, not_found):
{
"error": {
"code": "not_found",
"message": "Resource not found",
"details": null
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Mettre à jour un contact
requires authentication
Update partiel (PATCH). Tous les champs sont nullable. Les opt-ins peuvent être basculés par cette API.
Example request:
curl --request PATCH \
"https://eburniesend.com/api/v1/contacts/7c8f9d10-1234-4567-89ab-cdef01234567" \
--header "Authorization: Bearer nh_live_xxx" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"phone_e164\": \"+22505XXXXXXXX\",
\"email\": \"[email protected]\",
\"first_name\": \"Jean\",
\"last_name\": \"Diallo\",
\"external_id\": \"CUST-1234\",
\"language\": \"fr\",
\"country_code\": \"CI\",
\"opt_in_whatsapp\": false,
\"opt_in_sms\": true,
\"opt_in_email\": true,
\"tags\": [
\"vip\"
],
\"custom_attributes\": {
\"age\": 42
}
}"
const url = new URL(
"https://eburniesend.com/api/v1/contacts/7c8f9d10-1234-4567-89ab-cdef01234567"
);
const headers = {
"Authorization": "Bearer nh_live_xxx",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"phone_e164": "+22505XXXXXXXX",
"email": "[email protected]",
"first_name": "Jean",
"last_name": "Diallo",
"external_id": "CUST-1234",
"language": "fr",
"country_code": "CI",
"opt_in_whatsapp": false,
"opt_in_sms": true,
"opt_in_email": true,
"tags": [
"vip"
],
"custom_attributes": {
"age": 42
}
};
fetch(url, {
method: "PATCH",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, updated):
{
"data": {
"id": "7c8f9d10-...",
"phone_e164": "+22505XXXXXXXX",
"first_name": "Jean"
}
}
Example response (401, unauthorized):
{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key",
"details": null
}
}
Example response (404, not_found):
{
"error": {
"code": "not_found",
"message": "Resource not found",
"details": null
}
}
Example response (422, validation_failed):
{
"error": {
"code": "validation_failed",
"message": "The given data was invalid.",
"details": {
"fields": {
"email": [
"The email must be a valid email address."
]
}
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Supprimer un contact
requires authentication
Suppression définitive (hard delete). Les messages déjà envoyés à ce contact restent dans l'historique avec leur snapshot.
Example request:
curl --request DELETE \
"https://eburniesend.com/api/v1/contacts/7c8f9d10-1234-4567-89ab-cdef01234567" \
--header "Authorization: Bearer nh_live_xxx" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://eburniesend.com/api/v1/contacts/7c8f9d10-1234-4567-89ab-cdef01234567"
);
const headers = {
"Authorization": "Bearer nh_live_xxx",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, deleted):
Empty response
Example response (401, unauthorized):
{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key",
"details": null
}
}
Example response (404, not_found):
{
"error": {
"code": "not_found",
"message": "Resource not found",
"details": null
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Messages
Lister les messages
requires authentication
Retourne les messages du tenant courant, triés par date de création décroissante. Pagination cursor (max 200/page).
Example request:
curl --request GET \
--get "https://eburniesend.com/api/v1/messages?per_page=50&status=delivered&channel=sms&recipient=%2B2250555" \
--header "Authorization: Bearer nh_live_xxx" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://eburniesend.com/api/v1/messages"
);
const params = {
"per_page": "50",
"status": "delivered",
"channel": "sms",
"recipient": "+2250555",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer nh_live_xxx",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, success):
{
"data": [],
"links": {
"first": "...",
"last": "...",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 1,
"per_page": 50,
"to": 0,
"total": 0
}
}
Example response (401, unauthorized):
{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key",
"details": null
}
}
Example response (429, rate_limited):
{
"error": {
"code": "rate_limit_exceeded",
"message": "Too many requests",
"details": {
"retry_after": 60
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Envoyer un message
requires authentication
Crée et dispatch un message sur le canal demandé. Le pricing v2
débite automatiquement les CreditLots FIFO (mode pack) ou
incrémente le compteur Postpaid (mode postpaid). Le tenant doit
avoir un mode d'accès actif sur le canal — sinon HTTP 403.
Idempotency via header HTTP Idempotency-Key (recommandé) ou
champ idempotency_key du body. Une clé déjà consommée renvoie
le Message existant avec son statut courant.
Email — signature tenant : si le tenant a configuré une
signature email dans /app/settings/email-sender, elle est
appendée automatiquement au corps HTML de tous les emails envoyés
via cette API (séparateur <hr> injecté entre le corps et la
signature). Aucun champ à fournir côté client.
Email — pièces jointes : utiliser le champ attachments
(parité UI). Chaque path doit provenir d'un POST /api/v1/uploads
préalable (scope strict tenant ; un path d'un autre tenant est
silencieusement ignoré côté serveur).
Example request:
curl --request POST \
"https://eburniesend.com/api/v1/messages" \
--header "Authorization: Bearer nh_live_xxx" \
--header "Idempotency-Key: UUID/string 8-128 caractères pour idempotence. Recommandé pour tous les POST. Example: 8f3a2b91-7c4d-4e8f-a1b2-3c4d5e6f7a8b" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"channel\": \"sms\",
\"operator\": \"mtn_ci\",
\"to\": \"+22505XXXXXXXX\",
\"subject\": \"Confirmation commande #1234\",
\"content\": \"Votre code de validation est 1234.\",
\"template\": \"bulletin_de_note\",
\"language\": \"fr\",
\"variables\": [
\"Jean\",
\"EduCab\"
],
\"message_type\": \"transactional\",
\"priority\": 5,
\"idempotency_key\": \"order-1234-confirm\",
\"client_reference\": \"ORDER-1234\",
\"metadata\": {
\"template_header_data\": {
\"link\": \"https:\\/\\/example.com\\/file.pdf\",
\"filename\": \"bulletin.pdf\"
}
},
\"attachments\": [
{
\"path\": \"tenants\\/7\\/email_attachments\\/abc.pdf\",
\"name\": \"facture.pdf\",
\"mime\": \"application\\/pdf\"
}
]
}"
const url = new URL(
"https://eburniesend.com/api/v1/messages"
);
const headers = {
"Authorization": "Bearer nh_live_xxx",
"Idempotency-Key": "UUID/string 8-128 caractères pour idempotence. Recommandé pour tous les POST. Example: 8f3a2b91-7c4d-4e8f-a1b2-3c4d5e6f7a8b",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"channel": "sms",
"operator": "mtn_ci",
"to": "+22505XXXXXXXX",
"subject": "Confirmation commande #1234",
"content": "Votre code de validation est 1234.",
"template": "bulletin_de_note",
"language": "fr",
"variables": [
"Jean",
"EduCab"
],
"message_type": "transactional",
"priority": 5,
"idempotency_key": "order-1234-confirm",
"client_reference": "ORDER-1234",
"metadata": {
"template_header_data": {
"link": "https:\/\/example.com\/file.pdf",
"filename": "bulletin.pdf"
}
},
"attachments": [
{
"path": "tenants\/7\/email_attachments\/abc.pdf",
"name": "facture.pdf",
"mime": "application\/pdf"
}
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201, created):
{
"data": {
"id": "a1c9fe84-ec82-4f87-b257-acd90ae9f998",
"status": "queued",
"channel": "sms",
"cost": 14,
"currency": "XOF",
"metadata": {
"pricing_mode": "pack"
}
}
}
Example response (401, unauthorized):
{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key",
"details": null
}
}
Example response (402, insufficient_credits):
{
"error": {
"code": "insufficient_credits",
"message": "Tenant 7 : crédits insuffisants sur sms (besoin 14 XOF, dispo 0 XOF).",
"details": {
"tenant_id": 7,
"channel_code": "sms",
"amount_required_xof": 14,
"amount_available_xof": 0
}
}
}
Example response (402, postpaid_volume_cap_exceeded):
{
"error": {
"code": "postpaid_volume_cap_exceeded",
"message": "Tenant 12 : plafond mensuel Postpaid dépassé sur sms (contrat #5 : 50000/50000, +1 demandés).",
"details": {
"tenant_id": 12,
"channel_code": "sms",
"contract_id": 5,
"current_volume": 50000,
"requested_units": 1,
"monthly_cap": 50000
}
}
}
Example response (402, insufficient_funds):
{
"error": {
"code": "insufficient_funds",
"message": "Tenant v1 : solde insuffisant sur sms.",
"details": {
"tenant_id": 3,
"channel_code": "sms",
"amount_required": 14,
"balance_available": 0
}
}
}
Example response (403, channel_blocked):
{
"error": {
"code": "channel_blocked",
"message": "Aucun mode d'accès actif sur le canal sms.",
"details": {
"tenant_id": 7,
"channel_code": "sms",
"reason": "no_active_mode"
}
}
}
Example response (403, sender_id_not_authorized):
{
"error": {
"code": "sender_id_not_authorized",
"message": "No authorized sender ID for tenant 7 on gateway (http_sms_gateway/mtn).",
"details": {
"tenant_id": 7,
"gateway_id": 1,
"gateway_type": "http_sms_gateway",
"operator": "mtn"
}
}
}
Example response (404, template_not_found):
{
"error": {
"code": "not_found",
"message": "Resource not found",
"details": null
}
}
Example response (422, validation_failed):
{
"error": {
"code": "validation_failed",
"message": "The given data was invalid.",
"details": {
"fields": {
"to": [
"The to field is required."
]
}
}
}
}
Example response (422, no_active_contract):
{
"error": {
"code": "no_active_contract",
"message": "Tenant 12 : aucun contrat Postpaid actif sur le canal sms.",
"details": {
"tenant_id": 12,
"channel_code": "sms"
}
}
}
Example response (422, price_not_found):
{
"error": {
"code": "price_not_found",
"message": "Prix introuvable.",
"details": {
"tenant_id": 7,
"channel_id": 2,
"operator_id": 1,
"message_type_id": 1
}
}
}
Example response (429, rate_limited):
{
"error": {
"code": "rate_limit_exceeded",
"message": "Too many requests",
"details": {
"retry_after": 60
}
}
}
Example response (502, no_gateway_available):
{
"error": {
"code": "no_gateway_available",
"message": "Aucun gateway SMS disponible pour le tenant 7 (operator=orange) — preferred + fallback DOWN simultanément. tentés : [1, 2]",
"details": {
"tenant_id": 7,
"recipient_operator": "orange",
"attempted_gateway_ids": [
1,
2
]
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Envoi en lot (batch)
requires authentication
Envoie jusqu'à 100 messages en une seule requête. Chaque message
est traité indépendamment : un échec sur l'un n'arrête pas le
traitement des autres. Le response retourne un tableau
results[] où chaque entrée est soit {message: {...}} (succès,
HTTP 201 implicite par item) soit {error: {...}} (erreur item).
Idempotency : chaque item peut porter son propre
idempotency_key. Le header Idempotency-Key couvre toute la
requête batch (utile pour retry HTTP) — pas chaque item.
Example request:
curl --request POST \
"https://eburniesend.com/api/v1/messages/batch" \
--header "Authorization: Bearer nh_live_xxx" \
--header "Idempotency-Key: string Idempotence de la requête batch entière (8-128 chars). Example: batch-2026-05-17-001" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"messages\": [
{
\"channel\": \"sms\",
\"to\": \"+2250555\",
\"content\": \"Hello\"
}
]
}"
const url = new URL(
"https://eburniesend.com/api/v1/messages/batch"
);
const headers = {
"Authorization": "Bearer nh_live_xxx",
"Idempotency-Key": "string Idempotence de la requête batch entière (8-128 chars). Example: batch-2026-05-17-001",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"messages": [
{
"channel": "sms",
"to": "+2250555",
"content": "Hello"
}
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, mixed):
{
"data": {
"results": [
{
"message": {
"id": "...",
"status": "queued"
}
},
{
"error": {
"code": "validation_failed",
"message": "..."
}
}
]
}
}
Example response (401, unauthorized):
{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key",
"details": null
}
}
Example response (422, validation_failed):
{
"error": {
"code": "validation_failed",
"message": "The given data was invalid.",
"details": {
"fields": {
"messages": [
"The messages field is required."
]
}
}
}
}
Example response (429, rate_limited):
{
"error": {
"code": "rate_limit_exceeded",
"message": "Too many requests",
"details": {
"retry_after": 60
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Détails d'un message
requires authentication
Retourne le statut courant et l'historique complet d'un message identifié par son UUID.
Example request:
curl --request GET \
--get "https://eburniesend.com/api/v1/messages/a1c9fe84-ec82-4f87-b257-acd90ae9f998" \
--header "Authorization: Bearer nh_live_xxx" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://eburniesend.com/api/v1/messages/a1c9fe84-ec82-4f87-b257-acd90ae9f998"
);
const headers = {
"Authorization": "Bearer nh_live_xxx",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, success):
{
"data": {
"id": "a1c9fe84-ec82-4f87-b257-acd90ae9f998",
"status": "delivered",
"channel": "sms",
"recipient": "+22505XXXXXXXX"
}
}
Example response (401, unauthorized):
{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key",
"details": null
}
}
Example response (404, not_found):
{
"error": {
"code": "not_found",
"message": "Resource not found",
"details": null
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Notifications
Envoyer une notification métier multi-canal
requires authentication
Déclenche l'envoi de N messages selon les préférences canaux
configurées par le tenant pour cet event_type. Contrairement à
POST /api/v1/messages qui est low-level (1 message = 1 channel
explicite), cet endpoint route automatiquement vers les canaux
activés via tenant_notification_channel_preferences.
Configuration des préférences : voir Filament app
/app/settings/notifications (tenant) ou
/admin/tenants/{id} onglet préférences (Ebernate admin).
Idempotency-Key : si fournie, le dispatcher suffix par canal
({key}-sms, {key}-email...) côté MessageDispatcher pour
éviter les doublons en cas de retry consumer. Le replay d'un
appel avec la même clé renvoie les mêmes Messages (mêmes UUIDs).
Example request:
curl --request POST \
"https://eburniesend.com/api/v1/notifications" \
--header "Authorization: Bearer nh_live_xxx" \
--header "Idempotency-Key: string Clé d\'idempotence (UUID v4 ou alphanumérique 8-128 chars). Example: 01970b6d-2c40-7000-91a0-aaaaaaaaaaaa" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"event_type\": \"business.order_confirmed\",
\"recipient\": {
\"email\": \"[email protected]\",
\"phone_e164\": \"+22505XXXXXXXX\",
\"whatsapp_e164\": \"+22505XXXXXXXX\"
},
\"template_data\": {
\"order_id\": \"12345\",
\"amount_xof\": 50000
},
\"client_reference\": \"ORDER-12345\"
}"
const url = new URL(
"https://eburniesend.com/api/v1/notifications"
);
const headers = {
"Authorization": "Bearer nh_live_xxx",
"Idempotency-Key": "string Clé d'idempotence (UUID v4 ou alphanumérique 8-128 chars). Example: 01970b6d-2c40-7000-91a0-aaaaaaaaaaaa",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"event_type": "business.order_confirmed",
"recipient": {
"email": "[email protected]",
"phone_e164": "+22505XXXXXXXX",
"whatsapp_e164": "+22505XXXXXXXX"
},
"template_data": {
"order_id": "12345",
"amount_xof": 50000
},
"client_reference": "ORDER-12345"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201, created):
{
"data": {
"notification_id": "01970b6d-2c40-7000-91a0-aaaaaaaaaaaa",
"messages": [
{
"id": "01970b6d-2c40-7000-91a0-bbbbbbbbbbbb",
"channel": "sms",
"status": "queued"
},
{
"id": "01970b6d-2c40-7000-91a0-cccccccccccc",
"channel": "email",
"status": "queued"
}
]
},
"meta": {
"channels_configured": 3,
"channels_dispatched": 2,
"channels_skipped": [
{
"channel": "whatsapp",
"reason": "recipient_missing"
}
]
}
}
Example response (401, unauthorized):
{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key",
"details": null
}
}
Example response (422, validation_failed):
{
"error": {
"code": "validation_failed",
"message": "The given data was invalid.",
"details": {
"fields": {
"event_type": [
"The event type format is invalid. Use lowercase letters, digits, dots and underscores starting with a letter (e.g. business.order_confirmed)."
]
}
}
}
}
Example response (422, invalid_event_type):
{
"error": {
"code": "invalid_event_type",
"message": "Invalid event type 'badformat': format_invalid",
"details": {
"event_type": "badformat",
"reason": "format_invalid"
}
}
}
Example response (422, no_channels_configured):
{
"error": {
"code": "no_channels_configured",
"message": "No channels configured for tenant 7 on event 'business.order_confirmed'.",
"details": {
"tenant_id": 7,
"event_type": "business.order_confirmed"
}
}
}
Example response (422, all_channels_skipped):
{
"error": {
"code": "all_channels_skipped",
"message": "All 2 configured channels skipped for tenant 7 on event 'business.order_confirmed'.",
"details": {
"tenant_id": 7,
"event_type": "business.order_confirmed",
"skipped": [
{
"channel": "sms",
"reason": "recipient_missing"
},
{
"channel": "email",
"reason": "recipient_missing"
}
]
}
}
}
Example response (429, rate_limited):
{
"error": {
"code": "rate_limit_exceeded",
"message": "Too many requests",
"details": {
"retry_after": 60
}
}
}
Example response (502, no_gateway_available):
{
"error": {
"code": "no_gateway_available",
"message": "Aucun gateway SMS disponible pour le tenant 7 (operator=orange) — preferred + fallback DOWN simultanément. tentés : [1, 2]",
"details": {
"tenant_id": 7,
"recipient_operator": "orange",
"attempted_gateway_ids": [
1,
2
]
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Templates WhatsApp
Lister les templates WhatsApp
requires authentication
Retourne les templates WhatsApp du tenant (et les templates
plateforme is_shareable=true accessibles cross-tenant). Triés
par date de création décroissante.
Example request:
curl --request GET \
--get "https://eburniesend.com/api/v1/templates?per_page=50&status=approved&language=fr&category=UTILITY" \
--header "Authorization: Bearer nh_live_xxx" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://eburniesend.com/api/v1/templates"
);
const params = {
"per_page": "50",
"status": "approved",
"language": "fr",
"category": "UTILITY",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer nh_live_xxx",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, success):
{
"data": [],
"meta": {
"current_page": 1,
"per_page": 50,
"total": 0
}
}
Example response (401, unauthorized):
{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key",
"details": null
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Créer un template WhatsApp
requires authentication
Crée un template en statut draft. La soumission à Meta pour
approbation se fait dans un second temps via l'admin Filament
(un endpoint dédié sera disponible prochainement). Le tenant doit
avoir au moins un WhatsappNumber configuré (soit explicitement
via whatsapp_number_id, soit le default plateforme).
Example request:
curl --request POST \
"https://eburniesend.com/api/v1/templates" \
--header "Authorization: Bearer nh_live_xxx" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"whatsapp_number_id\": \"aabbccdd-1122-3344-5566-7788991122ff\",
\"name\": \"order_confirmation\",
\"language\": \"fr\",
\"category\": \"UTILITY\",
\"components\": [
{
\"type\": \"BODY\",
\"text\": \"Hello {{1}}, your order #{{2}} is confirmed.\"
}
]
}"
const url = new URL(
"https://eburniesend.com/api/v1/templates"
);
const headers = {
"Authorization": "Bearer nh_live_xxx",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"whatsapp_number_id": "aabbccdd-1122-3344-5566-7788991122ff",
"name": "order_confirmation",
"language": "fr",
"category": "UTILITY",
"components": [
{
"type": "BODY",
"text": "Hello {{1}}, your order #{{2}} is confirmed."
}
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201, created):
{
"data": {
"id": "11223344-...",
"name": "order_confirmation",
"status": "draft",
"category": "UTILITY"
}
}
Example response (401, unauthorized):
{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key",
"details": null
}
}
Example response (422, no_whatsapp_number):
{
"error": {
"code": "no_whatsapp_number",
"message": "Aucun numéro WhatsApp configuré pour ce tenant",
"details": null
}
}
Example response (422, validation_failed):
{
"error": {
"code": "validation_failed",
"message": "The given data was invalid.",
"details": {
"fields": {
"name": [
"The name format is invalid."
]
}
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Détails d'un template WhatsApp
requires authentication
Example request:
curl --request GET \
--get "https://eburniesend.com/api/v1/templates/11223344-aabb-ccdd-eeff-001122334455" \
--header "Authorization: Bearer nh_live_xxx" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://eburniesend.com/api/v1/templates/11223344-aabb-ccdd-eeff-001122334455"
);
const headers = {
"Authorization": "Bearer nh_live_xxx",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, success):
{
"data": {
"id": "11223344-...",
"name": "bulletin_de_note",
"language": "fr",
"status": "approved",
"category": "UTILITY",
"components": []
}
}
Example response (401, unauthorized):
{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key",
"details": null
}
}
Example response (404, not_found):
{
"error": {
"code": "not_found",
"message": "Resource not found",
"details": null
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Uploads
Upload d'une pièce jointe pour email.
requires authentication
Example request:
curl --request POST \
"https://eburniesend.com/api/v1/uploads" \
--header "Authorization: Bearer nh_live_xxx" \
--header "Content-Type: multipart/form-data" \
--header "Accept: application/json" \
--form "file=@C:\Users\EDUCAB02\AppData\Local\Temp\phpF05E.tmp" const url = new URL(
"https://eburniesend.com/api/v1/uploads"
);
const headers = {
"Authorization": "Bearer nh_live_xxx",
"Content-Type": "multipart/form-data",
"Accept": "application/json",
};
const body = new FormData();
body.append('file', document.querySelector('input[name="file"]').files[0]);
fetch(url, {
method: "POST",
headers,
body,
}).then(response => response.json());Example response (201, created):
{
"data": {
"path": "tenants/7/email_attachments/a1c79606-b6a6-47cc-956b-ea058f8ecbe3.pdf",
"name": "facture-2026.pdf",
"mime": "application/pdf",
"size": 12345
}
}
Example response (401, unauthorized):
{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key",
"details": null
}
}
Example response (422, validation_failed):
{
"error": {
"code": "validation_failed",
"message": "The given data was invalid.",
"details": {
"fields": {
"file": [
"The file must be a file of type: pdf, png, jpg, jpeg, gif, doc, docx, xls, xlsx."
]
}
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Webhooks sortants
Lister les endpoints webhook
requires authentication
Retourne les endpoints webhook configurés par le tenant. Ces endpoints reçoivent les notifications d'événements (sent / delivered / failed / etc.) via le WebhookDispatcher.
Example request:
curl --request GET \
--get "https://eburniesend.com/api/v1/webhooks" \
--header "Authorization: Bearer nh_live_xxx" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://eburniesend.com/api/v1/webhooks"
);
const headers = {
"Authorization": "Bearer nh_live_xxx",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, success):
{
"data": [],
"meta": {
"current_page": 1,
"per_page": 50,
"total": 0
}
}
Example response (401, unauthorized):
{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key",
"details": null
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Créer un endpoint webhook
requires authentication
Enregistre une URL HTTPS qui recevra les notifications
d'événements eBurnieSend. Chaque payload est signé HMAC-SHA256
avec le secret, transmis dans le header
X-eBurnieSend-Signature: sha256=<hex>. Voir la doc de
vérification de signature côté consumer pour l'algo exact.
Sécurité du secret :
- Si vous ne fournissez pas
secret, le système en génère un automatiquement (whsec_+ 42 chars aléatoires = 48 chars). - Le secret est retourné UNE SEULE FOIS dans
meta.secret_plaintextde cette réponse. Stockez-le immédiatement côté serveur consumer ; les endpointsGETetPATCHne le révéleront JAMAIS (sécurité par défaut). - Pour récupérer/rotater un secret oublié : actuellement supprimer
et recréer l'endpoint. Un endpoint dédié
rotate-secretest prévu prochainement.
Example request:
curl --request POST \
"https://eburniesend.com/api/v1/webhooks" \
--header "Authorization: Bearer nh_live_xxx" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"name\": \"Production webhook\",
\"url\": \"https:\\/\\/api.example.com\\/webhooks\\/ebs\",
\"subscribed_events\": [
\"message.delivered\",
\"message.failed\"
],
\"secret\": \"whsec_my_custom_secret_at_least_32_chars_long_here\",
\"timeout_seconds\": 10,
\"max_retries\": 5
}"
const url = new URL(
"https://eburniesend.com/api/v1/webhooks"
);
const headers = {
"Authorization": "Bearer nh_live_xxx",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "Production webhook",
"url": "https:\/\/api.example.com\/webhooks\/ebs",
"subscribed_events": [
"message.delivered",
"message.failed"
],
"secret": "whsec_my_custom_secret_at_least_32_chars_long_here",
"timeout_seconds": 10,
"max_retries": 5
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201, created):
{
"data": {
"id": "ff112233-...",
"name": "Production webhook",
"url": "https://api.example.com/webhooks/ebs",
"subscribed_events": [
"message.delivered"
],
"is_active": true
},
"meta": {
"secret_plaintext": "whsec_abcdef0123456789abcdef0123456789abcdef0123",
"warning": "This secret will not be shown again. Store it securely now. Use it to verify the X-eBurnieSend-Signature header on incoming webhook requests."
}
}
Example response (401, unauthorized):
{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key",
"details": null
}
}
Example response (422, validation_failed):
{
"error": {
"code": "validation_failed",
"message": "The given data was invalid.",
"details": {
"fields": {
"url": [
"The url must be a valid HTTPS URL."
],
"secret": [
"The secret must be at least 32 characters."
]
}
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Détails d'un endpoint webhook
requires authentication
Example request:
curl --request GET \
--get "https://eburniesend.com/api/v1/webhooks/ff112233-4455-6677-8899-aabbccddeeff" \
--header "Authorization: Bearer nh_live_xxx" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://eburniesend.com/api/v1/webhooks/ff112233-4455-6677-8899-aabbccddeeff"
);
const headers = {
"Authorization": "Bearer nh_live_xxx",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, success):
{
"data": {
"id": "ff112233-...",
"name": "Production webhook",
"url": "https://api.example.com/webhooks/ebs",
"subscribed_events": [
"message.delivered",
"message.failed"
]
}
}
Example response (401, unauthorized):
{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key",
"details": null
}
}
Example response (404, not_found):
{
"error": {
"code": "not_found",
"message": "Resource not found",
"details": null
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Mettre à jour un endpoint webhook
requires authentication
Update partiel (PATCH). Le secret n'est PAS modifiable via
cette API — pour rotation, supprimer + recréer l'endpoint.
Example request:
curl --request PATCH \
"https://eburniesend.com/api/v1/webhooks/ff112233-4455-6677-8899-aabbccddeeff" \
--header "Authorization: Bearer nh_live_xxx" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"name\": \"Production webhook v2\",
\"url\": \"https:\\/\\/api.example.com\\/webhooks\\/ebs-v2\",
\"subscribed_events\": [
\"message.delivered\",
\"message.failed\"
],
\"is_active\": true,
\"timeout_seconds\": 15,
\"max_retries\": 3
}"
const url = new URL(
"https://eburniesend.com/api/v1/webhooks/ff112233-4455-6677-8899-aabbccddeeff"
);
const headers = {
"Authorization": "Bearer nh_live_xxx",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "Production webhook v2",
"url": "https:\/\/api.example.com\/webhooks\/ebs-v2",
"subscribed_events": [
"message.delivered",
"message.failed"
],
"is_active": true,
"timeout_seconds": 15,
"max_retries": 3
};
fetch(url, {
method: "PATCH",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, updated):
{
"data": {
"id": "ff112233-...",
"name": "Production webhook v2",
"is_active": true
}
}
Example response (401, unauthorized):
{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key",
"details": null
}
}
Example response (404, not_found):
{
"error": {
"code": "not_found",
"message": "Resource not found",
"details": null
}
}
Example response (422, validation_failed):
{
"error": {
"code": "validation_failed",
"message": "The given data was invalid.",
"details": {
"fields": {
"url": [
"The url must be a valid HTTPS URL."
]
}
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Supprimer un endpoint webhook
requires authentication
Suppression définitive. L'historique webhook_deliveries reste
intact pour audit.
Example request:
curl --request DELETE \
"https://eburniesend.com/api/v1/webhooks/ff112233-4455-6677-8899-aabbccddeeff" \
--header "Authorization: Bearer nh_live_xxx" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://eburniesend.com/api/v1/webhooks/ff112233-4455-6677-8899-aabbccddeeff"
);
const headers = {
"Authorization": "Bearer nh_live_xxx",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, deleted):
Empty response
Example response (401, unauthorized):
{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key",
"details": null
}
}
Example response (404, not_found):
{
"error": {
"code": "not_found",
"message": "Resource not found",
"details": null
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.