Aller au contenu

JavaScript / Node.js

Node 18+ a fetch natif — pas de dépendance nécessaire. Exemples en fetch et axios (toujours courant sur les codebases plus anciens).

const API_KEY = process.env.LISOLOO_API_KEY;
const API_URL = "$BASE_URL/api/v1/lisoloo/sms-api";
const HEADERS = {
"app-key": API_KEY,
"Content-Type": "application/json",
};
const r = await fetch(`${API_URL}/send`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
to: ["+243998857000"],
message: "Bonjour de Lisoloo !",
sender_id: "MYAPP",
}),
});
if (!r.ok) {
const err = await r.json();
throw new Error(`Échec SMS : ${err.error_code} ${err.message}`);
}
const { data } = await r.json();
console.log(data.message_id);
type SendingType = "immediate" | "scheduled" | "recurring";
interface ScheduledDate { date: string; time: string }
interface RecurringSchedule {
start_date: string;
end_date: string;
frequency: "daily" | "weekly" | "monthly";
interval: number;
times: string[];
}
interface SendOptions {
to: string[];
message: string;
sender_id?: string;
sending_type?: SendingType;
scheduled_dates?: ScheduledDate[];
recurring_schedule?: RecurringSchedule;
callback_url?: string;
}
export class LisolooClient {
constructor(
private readonly apiKey: string,
private readonly baseUrl = "$BASE_URL/api/v1/lisoloo/sms-api",
private readonly timeoutMs = 10_000,
) {}
private headers() {
return {
"app-key": this.apiKey,
"Content-Type": "application/json",
};
}
private async request<T>(
path: string,
init: RequestInit = {},
): Promise<T> {
const ctrl = AbortSignal.timeout(this.timeoutMs);
const r = await fetch(`${this.baseUrl}${path}`, {
...init,
headers: this.headers(),
signal: ctrl,
});
if (!r.ok) {
const body = await r.json().catch(() => ({} as Record<string, unknown>));
throw Object.assign(new Error(`Lisoloo ${r.status}`), { status: r.status, body });
}
return r.json() as Promise<T>;
}
async send(opts: SendOptions) {
const body = { sending_type: "immediate", ...opts };
const r = await this.request<{ data: { message_id: string; status: string; total_cost: number } }>(
"/send",
{ method: "POST", body: JSON.stringify(body) },
);
return r.data;
}
async getStatus(messageId: string) {
const r = await this.request<{ data: unknown }>(`/status/${messageId}`);
return r.data;
}
async getBalance() {
const r = await this.request<{ data: { balance: number; available_sms: number } }>(`/balance`);
return r.data;
}
}

Utilisation :

const client = new LisolooClient(process.env.LISOLOO_API_KEY!);
// Instantané
const sent = await client.send({
to: ["+243998857000"], message: "Bonjour !", sender_id: "MYAPP",
});
// Planifié
await client.send({
to: ["+243998857000"], message: "Rappel",
sending_type: "scheduled",
scheduled_dates: [{ date: "2026-06-01", time: "08:00" }],
});
// Récurrent
await client.send({
to: ["+243998857000"], message: "Hebdomadaire",
sending_type: "recurring",
recurring_schedule: {
start_date: "2026-06-01", end_date: "2026-12-31",
frequency: "weekly", interval: 1, times: ["09:00"],
},
});
async function sendWithRetry(client: LisolooClient, opts: SendOptions, max = 3) {
for (let attempt = 0; attempt < max; attempt++) {
try {
return await client.send(opts);
} catch (err: any) {
if (err.status !== 429 || attempt === max - 1) throw err;
const wait = (err.body?.retry_after ?? 60) * 1000;
await new Promise(res => setTimeout(res, wait));
}
}
throw new Error("inatteignable");
}
import express from "express";
const app = express();
app.use(express.json());
const EXPECTED_USER = process.env.LISOLOO_WEBHOOK_USER!;
const EXPECTED_PASS = process.env.LISOLOO_WEBHOOK_PASS!;
const seen = new Set<string>(); // en production : Redis
app.post("/lisoloo/webhook", (req, res) => {
const auth = req.headers.authorization ?? "";
if (!auth.startsWith("Basic ")) return res.sendStatus(401);
const [user, pass] = Buffer.from(auth.slice(6), "base64").toString().split(":");
if (user !== EXPECTED_USER || pass !== EXPECTED_PASS) {
return res.sendStatus(401);
}
const { event_id, event_type, message_id, data } = req.body;
if (seen.has(event_id)) return res.sendStatus(204);
seen.add(event_id);
// … votre logique métier …
res.sendStatus(204);
});
app.listen(3000);