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.