Developer docs

IotaBot WhatsApp API

Send and receive WhatsApp messages from your code — on every number you connected.

Get an API key

Send WhatsApp messages with Node.js

Send and receive from Node.js — built-in fetch and Express.

  1. Create an API key (a test key never sends) and a webhook endpoint in Developers.
  2. Set IOTABOT_API_KEY and IOTABOT_WEBHOOK_SECRET (the endpoint's whsec_… secret) in your environment.
  3. Run the program below. Point the endpoint at /iotabot/webhook on your server.
app.js
// npm install express      (Node.js 18+: fetch is built in)
const crypto = require("crypto");
const express = require("express");

const API = "https://api.iotabot.com/v1";
const KEY = process.env.IOTABOT_API_KEY;
const SECRET = process.env.IOTABOT_WEBHOOK_SECRET;   // the endpoint's signing secret (whsec_…)

async function send(body) {
  // One Idempotency-Key per message: reuse it if you retry, so it is never sent twice.
  const res = await fetch(`${API}/messages`, {
    method: "POST",
    headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID() },
    body: JSON.stringify(body),
  });
  const { data, error } = await res.json();
  if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
  return data;
}

// 1. Start a conversation with an approved template — works for any number, any time.
send({
  channel: "whatsapp",
  to: "+919812345678",
  template: { name: "order_update", language: "en_US", parameters: ["#1042", "Friday"] },
}).then(
  m => console.log("queued", m.id, "in conversation", m.conversation_id),
  err => console.error("send refused:", err.message),   // e.g. template_not_approved — use your own
);

// 2. Receive messages and reply within the 24-hour window.
function verified(raw, header) {
  const parts = header.split(",").map(p => p.split("="));
  const t = Number(parts.find(([k]) => k === "t")?.[1]);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;   // older than 5 minutes
  const expected = crypto.createHmac("sha256", SECRET).update(`${t}.${raw}`).digest();
  return parts.filter(([k]) => k === "v1").some(([, v]) => {
    const got = Buffer.from(v, "hex");
    return got.length === expected.length && crypto.timingSafeEqual(got, expected);
  });
}

const app = express();
app.post("/iotabot/webhook", express.raw({ type: "application/json" }), async (req, res) => {
  const event = JSON.parse(req.body);
  if (event.type === "endpoint.verification") {                  // sent once, when you add the endpoint
    return res.json({ challenge: event.data.challenge });
  }
  if (!verified(req.body, req.get("IotaBot-Signature") ?? "")) return res.sendStatus(401);
  res.sendStatus(200);                                           // answer fast, then do the work
  if (event.type === "message.received") {
    const { conversation, message } = event.data;
    console.log(conversation.contact.phone, "wrote:", message.text);
    await send({ conversation_id: conversation.id, text: "Thanks — we are on it!" });
  }
});
app.listen(3000);