Quickstart
Send a REST SMS and handle the 202 response.
Environment variables
| Variable | Example | Role |
|---|---|---|
| COUSSEMA_API_KEY | csm_live_xxxxxxxxxxxx_xxx | API key generated from the dashboard. |
| COUSSEMA_BASE_URL | https://api.coussema.com | Production API host. Add the /v1 prefix explicitly in REST requests. |
| COUSSEMA_WEBHOOK_SECRET | csm_whsec_... | Shared secret to verify webhook callbacks. |
Before the call
- Use the REST API with
fetchfrom your Node.js backend; the public npm SDK is not available yet. - Create an organization and get an API key with the “SMS sending only” profile from the dashboard.
- Keep the API key on the server side only. Do not put it in a browser bundle.
Idempotency-Keyis required on everyPOST /v1/sms/send; reuse the same value only to retry the same send.- Correlate errors through
x-request-idand theX-RateLimit-*headers.
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,
});202 response
POST /v1/sms/sendreturns a202 Acceptedacceptance, not proof of delivery.queued: trueandstatus: acceptedconfirm that Coussema stored the message before the worker dispatch.message.idis the durable Coussema reference.provider.messageIdalways staysnullin the public response.- Do not code a strict
statusCode === 200test: accept2xxresponses that follow the contract.
| Field | Value |
|---|---|
| success | true |
| queued | true |
| status | accepted |
| count | 1 |
| remainingQuota | 999 |
| message.id | Coussema UUID |
| provider.code | coussema |
| provider.messageId | null |
| provider.httpStatus | 202 |
Prepare the 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);
}