Skip to content

JavaScript / Node.js

Node 18+ has fetch built in — no dependency needed. Examples in fetch and axios (still common in older codebases).

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: "Hello from Lisoloo!",
sender_id: "MYAPP",
}),
});
if (!r.ok) {
const err = await r.json();
throw new Error(`SMS failed: ${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;
}
}

Usage:

const client = new LisolooClient(process.env.LISOLOO_API_KEY!);
// Instant
const sent = await client.send({
to: ["+243998857000"], message: "Hello!", sender_id: "MYAPP",
});
// Scheduled
await client.send({
to: ["+243998857000"], message: "Reminder",
sending_type: "scheduled",
scheduled_dates: [{ date: "2026-06-01", time: "08:00" }],
});
// Recurring
await client.send({
to: ["+243998857000"], message: "Weekly",
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("unreachable");
}
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>(); // in 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);
// … your business logic …
res.sendStatus(204);
});
app.listen(3000);