Démarrage rapide
Envoyez un SMS REST et traitez la réponse 202.
Variables d’environnement
| Variable | Exemple | Rôle |
|---|---|---|
| COUSSEMA_API_KEY | csm_live_xxxxxxxxxxxx_xxx | Clé API générée depuis le dashboard. |
| COUSSEMA_BASE_URL | https://api.coussema.com | Hôte API de production. Ajoutez explicitement le préfixe /v1 dans les requêtes REST. |
| COUSSEMA_WEBHOOK_SECRET | csm_whsec_... | Secret partagé pour vérifier les callbacks webhook. |
Avant l’appel
- Utiliser l’API REST avec
fetchdepuis votre backend Node.js; le SDK npm public n’est pas encore disponible. - Créer une organisation et récupérer une clé API avec le profil « Envoi SMS uniquement » depuis le dashboard.
- Conserver la clé API côté serveur uniquement. Ne l’intégrez pas dans un bundle navigateur.
Idempotency-Keyest obligatoire sur chaquePOST /v1/sms/send; réutilisez la même valeur uniquement pour retenter le même envoi.- Corréler les erreurs via
x-request-idet les headersX-RateLimit-*.
fetch()
javascript
import crypto from 'node:crypto';
const baseUrl = process.env.COUSSEMA_BASE_URL ?? 'https://api.coussema.com';
const apiKey = process.env.COUSSEMA_API_KEY;
if (!apiKey) {
throw new Error('COUSSEMA_API_KEY is required.');
}
const response = await fetch(`${baseUrl}/v1/sms/send`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
'X-Request-Id': 'quickstart-send-001',
},
body: JSON.stringify({
to: '243810000000',
message: 'Votre code de verification est 482019.',
senderName: 'Coussema',
clientReference: 'welcome-001',
}),
});
const payload = await response.json();
if (!response.ok) {
throw new Error(`Coussema API returned ${response.status}.`);
}
console.log({
requestId: response.headers.get('x-request-id'),
remainingQuota: payload.remainingQuota,
queued: payload.queued,
status: payload.status,
});Réponse 202
POST /v1/sms/sendrenvoie une acceptation202 Accepted, pas une preuve de livraison.queued: trueetstatus: acceptedconfirment que Coussema a persisté le message avant dispatch worker.message.idest la référence Coussema durable.provider.messageIdreste toujoursnullsur la réponse publique.- Ne codez pas un test strict
statusCode === 200: acceptez les réponses2xxconformes au contrat.
| Champ | Valeur |
|---|---|
| success | true |
| queued | true |
| status | accepted |
| count | 1 |
| remainingQuota | 999 |
| message.id | UUID Coussema |
| provider.code | coussema |
| provider.messageId | null |
| provider.httpStatus | 202 |
Préparer les webhooks
javascript
import { createHmac, timingSafeEqual } from 'node:crypto';
function handleWebhook(rawBody, headers) {
const timestamp = headers.get('x-coussema-timestamp');
const signature = headers.get('x-coussema-signature');
const signingSecret = process.env.COUSSEMA_WEBHOOK_SECRET ?? '';
if (!timestamp || !signature || !signingSecret) {
throw new Error('Missing Coussema webhook signature data.');
}
const [version, incomingDigest = ''] = signature.split('=');
const timestampMs = Date.parse(timestamp);
const timestampIsFresh = Number.isFinite(timestampMs)
&& Math.abs(Date.now() - timestampMs) <= 5 * 60 * 1000;
const expectedDigest = createHmac('sha256', signingSecret)
.update(`${timestamp}.${rawBody}`, 'utf8')
.digest('hex');
const valid = timestampIsFresh
&& version === 'v1'
&& incomingDigest.length === expectedDigest.length
&& timingSafeEqual(Buffer.from(incomingDigest, 'utf8'), Buffer.from(expectedDigest, 'utf8'));
if (!valid) throw new Error('Invalid Coussema webhook signature.');
return JSON.parse(rawBody);
}