Intégrer le premier SMS en JavaScript
Ce guide montre comment appeler l’API REST avec `fetch`, lire sa réponse asynchrone et préparer la validation des webhooks. Le SDK npm public n’est pas encore disponible.
Pré-requis d’exécution
Le serveur qui appelle l’API doit conserver la clé API côté backend. Ne l’exposez jamais dans un bundle navigateur.
Comprendre la réponse 202
L’API privilégie maintenant la durabilité de la file avant la soumission fournisseur. Le code client doit donc traiter l’acceptation plateforme et le statut de livraison comme deux étapes séparées.
fetch(...)
API REST + fetch natifimport 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,
});COUSSEMA_API_KEY
Clé API générée depuis le dashboard.
csm_live_xxxxxxxxxxxx_xxxCOUSSEMA_BASE_URL
Hôte API de production. Ajoutez explicitement le préfixe `/v1` dans les requêtes REST.
https://api.coussema.comCOUSSEMA_WEBHOOK_SECRET
Secret partagé pour vérifier les callbacks webhook.
csm_whsec_...Préparer les webhooks
Quand l’envoi est critique, créez un endpoint webhook et vérifiez les callbacks signés sur le corps brut avec les primitives cryptographiques de Node.js.
Créer un endpoint
POST /v1/webhooks
Authorization: Bearer csm_live_xxxxxxxxxxxx_xxx
Content-Type: application/json
{
"url": "https://example.com/webhooks/coussema",
"subscribed_events": ["message.delivered", "message.failed"]
}
202 Accepted
{
"success": true,
"webhookEndpoint": {
"id": "7d5f1e88-6fd8-4ca7-a1a5-3b338f5f0f50",
"url": "https://example.com/webhooks/coussema",
"subscribed_events": ["message.delivered", "message.failed"],
"status": "active"
},
"signingSecret": "csm_whsec_..."
}Vérifier la signature
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);
}