Node.js Examples
Runnable snippets using Node.js (18+) with the built-in fetch. Every call goes to https://client.routeon.io/api/v1 and carries the X-API-Key header. Substitute <API_KEY> with a key from the Client portal.
Setup
const BASE = "https://client.routeon.io/api/v1";
const API_KEY = process.env.ROUTEON_API_KEY; // your key from the portal
async function api(method, path, body) {
const res = await fetch(`${BASE}${path}`, {
method,
headers: { "X-API-Key": API_KEY, "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json().catch(() => null);
return { status: res.status, json };
}
Pre-flight: channels, senders, balance
const channels = await api("GET", "/channels");
const senders = await api("GET", "/senders"); // look for status === 2 (Active)
const balance = await api("GET", "/balance");
console.log(channels.json, senders.json, balance.json);
Send an SMS
const { status, json } = await api("POST", "/messages", {
contact: "905063565285", // digits only, no "+"
channel: 1, // 1 = SMS
senderId: "Routeon",
payload: { text: "Hello from Routeon" },
webhook: "https://your.app/hooks/routeon",
clientInfo: "batchId=2026-06-30",
});
// status 202; json.data.messageId is your tracking handle
console.log(status, json.data.messageId, json.data.transactionId);
Send an OTP SMS
You generate and verify the code yourself; isOtp masks the body in logs and reports.
const code = String(Math.floor(1000 + Math.random() * 9000));
await api("POST", "/messages", {
contact: "905063565285",
channel: 1,
senderId: "Routeon",
payload: { text: `Your code is ${code}` },
isOtp: true,
});
// store `code` (hashed, with an expiry) and check it against what the user types
Track delivery: GET /edrs
Up to 10 messageIds per call; limit caps at 100.
const ids = ["6719b5f4-....", "4aab1947-...."]; // ≤ 10
const edrs = await api("GET", `/edrs?messageIds=${ids.join(",")}`);
for (const row of edrs.json.data ?? []) {
console.log(row.mdnStatus, row.isDelivered, row.finalCost, row.clientInfo);
}
Create an SMS campaign template
const tpl = await api("POST", "/broadcast/templates", {
name: "Demo SMS template",
description: "Welcome message",
extraRecipientList: [], filterList: [], tags: [],
contentType: 1,
content: {
flowSteps: [{
channelType: 1,
senderId: "Routeon",
contentPattern: "Hello {{attribute.name}}, this is a demo.",
deliveryCondition: "DELIVERY_SUCCESS",
timeout: 172800,
}],
},
});
Launch a campaign
// find the numeric template id
const list = await api("GET", "/broadcast/templates?limit=10&offset=0");
const templateId = list.json.data?.[0]?.id ?? 123;
await api("POST", "/broadcast/broadcasts", {
templateId,
launchTime: "", // "" = now; ISO-8601 = scheduled
webhook: "https://your.app/hooks/routeon",
recipientsList: [{ phoneNumber: "905063565285" }],
});
Import contacts
await api("POST", "/recipient/recipients", {
recipients: [
{ name: "Alice", phoneNumber: "48732231255", tags: ["demo"], attributes: { name: "Alice" } },
],
tag: "demo",
type: "ENRICH", // ENRICH | OVERWRITE | NOT_IMPORT
});
Webhook receiver
A complete Express receiver that handles all three event families, acknowledges fast, and deduplicates.
import express from "express";
const app = express();
app.use(express.json());
const seen = new Set(); // replace with a persistent store in production
app.post("/hooks/routeon", (req, res) => {
const b = req.body;
res.status(200).end(); // acknowledge immediately
if (b.event === "messageStatus") {
const id = b.message.messageId;
if (seen.has(id)) return; // at-least-once → dedupe
seen.add(id);
// update your records: b.message.status, b.message.cost, b.reason
} else if (b.event === "message") {
// inbound reply: b.message.payload.text (channel is a string here)
} else if (b.broadcastId != null) {
// campaign status: b.status ("FINISHED" | "FAILED" | ...)
}
});
app.listen(8080, () => console.log("listening on :8080"));
Error handling and retries
async function apiWithRetry(method, path, body, attempts = 3) {
for (let i = 0; i < attempts; i++) {
const { status, json } = await api(method, path, body);
if (json?.error) {
// envelope-level error — log the requestId for support
console.error("API error:", json.data?.message, "requestId:", json.data?.requestId);
}
if (status < 500) return { status, json }; // 2xx/4xx: do not retry
await new Promise((r) => setTimeout(r, [1000, 4000, 16000][i] ?? 16000)); // 5xx backoff
}
throw new Error(`${method} ${path} failed after ${attempts} attempts`);
}
Channel-level failures (e.g. REJECTD, UNDELIV) are not returned on the send call — it responds 202. Watch for them asynchronously on the messageStatus webhook or in GET /edrs.