Quickstart

Send a REST SMS and handle the 202 response.

Environment variables

VariableExampleRole
COUSSEMA_API_KEYcsm_live_xxxxxxxxxxxx_xxxAPI key generated from the dashboard.
COUSSEMA_BASE_URLhttps://api.coussema.comProduction API host. Add the /v1 prefix explicitly in REST requests.
COUSSEMA_WEBHOOK_SECRETcsm_whsec_...Shared secret to verify webhook callbacks.

Before the call

  • Use the REST API with fetch from 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-Key is required on every POST /v1/sms/send; reuse the same value only to retry the same send.
  • Correlate errors through x-request-id and the X-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/send returns a 202 Accepted acceptance, not proof of delivery.
  • queued: true and status: accepted confirm that Coussema stored the message before the worker dispatch.
  • message.id is the durable Coussema reference. provider.messageId always stays null in the public response.
  • Do not code a strict statusCode === 200 test: accept 2xx responses that follow the contract.
FieldValue
successtrue
queuedtrue
statusaccepted
count1
remainingQuota999
message.idCoussema UUID
provider.codecoussema
provider.messageIdnull
provider.httpStatus202

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);
}