API Specification v1

REST API Reference

The SmsNova API is developer-first and organized around standard REST. Send SMS through paired Android phones, read received messages, filter by specific sender numbers, and receive real-time webhook push events.

Base URL:https://sms.ullass.com/api/v1

Authentication

All requests require your secret organization API Key transmitted via the standard HTTP Authorization header using the Bearer scheme. You can create and revoke keys in your API Keys Dashboard.

# Header Format
Authorization: Bearer sn_live_9a8b7c6d5e4f3a2b1c0d

1. Dispatching Outbound SMS

POST/messages/sendAlias: /messages

Queues an outgoing SMS message and pushes it instantly to your paired Android phone over WebSocket. The phone's native telephony hardware dispatches the SMS using the designated SIM slot or smart cost-optimized routing.

Request Body (JSON)

{
  "to": "+8801712345678",        // Recipient phone in E.164 or local format ("017...")
  "message": "Your PosNova verification code is 492019. Valid for 5 minutes.",
  "simSlot": 0,                  // Optional: 0 (SIM 1), 1 (SIM 2), or omitted for Auto-Routing
  "priority": "high",            // Optional: "high" (OTPs, alarms) | "normal" (Promotional)
  "metadata": {                  // Optional: Pass arbitrary key-value pairs for webhooks
    "orderId": "ORD-1092",
    "customerId": "CUST-881"
  }
}

Response (202 Accepted)

{
  "success": true,
  "messageId": "msg_1727201948_a9f1b2c",
  "status": "queued",
  "recipient": "+8801712345678",
  "deviceId": "dev_samsung_a54_node1",
  "simSlot": 0,
  "createdAt": "2026-09-25T07:45:00.000Z"
}

2. Reading & Querying Phone SMS

Query messages received on your connected Android devices. You can read all inbox SMS, filter by specific customer phone numbers, retrieve incoming bank/MFS payment alerts (bKash, Nagad, Rocket), or search message text.

GET/messages

Lists messages matching your query filters with pagination support.

Query Parameters

ParameterTypeExampleDescription
fromstring+8801712345678 or bKashFilter by sender phone number or alphanumeric sender ID (e.g. bKash, 16247, 16167).
tostring+8801700000001Filter by the receiving SIM phone number.
typestringinbound | outboundFilter by direction. Use inbound to read SMS received on your phone.
searchstringTrxID or POSNOVACase-insensitive substring search in the SMS content.
limitnumber20 (Max: 100)Number of messages per page (default: 20).
offsetnumber0Pagination offset.

Example: Read SMS from a Specific Customer

curl -X GET "https://sms.ullass.com/api/v1/messages?from=%2B8801712345678&type=inbound" \
  -H "Authorization: Bearer sn_live_9a8b7c6d5e4f3a2b1c0d"

Example: Read Incoming bKash / Bank Payment SMS

curl -X GET "https://sms.ullass.com/api/v1/messages?from=bKash&type=inbound&limit=10" \
  -H "Authorization: Bearer sn_live_9a8b7c6d5e4f3a2b1c0d"

Response (200 OK)

{
  "success": true,
  "total": 1,
  "limit": 20,
  "offset": 0,
  "messages": [
    {
      "id": "msg_1727202101_bkash",
      "type": "inbound",
      "from": "bKash",
      "senderNumber": "16247",
      "to": "+8801700000001",
      "body": "You have received Tk 1,500.00 from 01712345678. Ref POSNOVA-1082. Fee Tk 0.00. Balance Tk 48,210.00. TrxID 9A8B7C6D5E at 24/09/2026 20:45",
      "simSlot": 0,
      "status": "received",
      "receivedAt": "2026-09-24T20:45:10.000Z",
      "deviceId": "dev_samsung_a54_node1",
      "parsedPayment": {
        "provider": "bKash",
        "amount": 1500.0,
        "currency": "BDT",
        "trxId": "9A8B7C6D5E",
        "senderPhone": "01712345678",
        "reconciled": true
      }
    }
  ]
}
GET/messages/:id

Retrieves the full record and status of a single message by its unique ID.

3. Webhooks & Real-Time Inbound Callbacks

Instead of polling /messages, configure a Webhook URL in your Webhooks Dashboard. SmsNova will deliver an immediate HTTP POST payload to your endpoint whenever a phone receives an SMS or completes delivery.

EVENTpayment.received
Regex Parsed in < 2ms

Fires when an incoming SMS matches one of the MFS regex patterns (bKash, Nagad, Rocket, M-Pesa, OPay). Contains fully structured payment metadata ready for zero-latency order reconciliation.

{
  "event": "payment.received",
  "id": "evt_91028401",
  "deviceId": "dev_samsung_a54_node1",
  "timestamp": "2026-09-24T20:45:10.000Z",
  "data": {
    "provider": "bKash",
    "amount": 1500.00,
    "currency": "BDT",
    "trxId": "9A8B7C6D5E",
    "senderPhone": "01712345678",
    "simSlot": 0,
    "rawText": "You have received Tk 1,500.00 from 01712345678. Ref POSNOVA-1082. Fee Tk 0.00. Balance Tk 48,210.00. TrxID 9A8B7C6D5E at 24/09/2026 20:45"
  }
}
EVENTsms.received

Fires for any general inbound SMS received on your phone from any customer or unknown number.

{
  "event": "sms.received",
  "id": "evt_91028402",
  "deviceId": "dev_samsung_a54_node1",
  "timestamp": "2026-09-24T21:05:00.000Z",
  "data": {
    "from": "+8801712345678",
    "to": "+8801700000001",
    "body": "Amar order delivery kobe hobe? Order ID #4912",
    "simSlot": 0
  }
}

Webhook Security & HMAC SHA-256 Verification

Every webhook request includes an X-SmsNova-Signature header generated using HMAC SHA-256 with your Webhook Secret. Always verify this signature in your server before processing transactions.

Node.js / Express Webhook Receiver

import express from "express";
import crypto from "crypto";

const app = express();
app.use(express.json());

const WEBHOOK_SECRET = process.env.SMSNOVA_WEBHOOK_SECRET;

app.post("/api/webhooks/smsnova", (req, res) => {
  const signature = req.headers["x-smsnova-signature"];
  const computedSignature = crypto
    .createHmac("sha256", WEBHOOK_SECRET)
    .update(JSON.stringify(req.body))
    .digest("hex");

  if (signature !== computedSignature) {
    return res.status(401).send("Invalid Webhook Signature");
  }

  const { event, data } = req.body;

  if (event === "payment.received") {
    console.log(`✅ Verified ${data.provider} Tk ${data.amount} from ${data.senderPhone} (TrxID: ${data.trxId})`);
    // Reconcile order in your database e.g., PosNova Order Mark as Paid
  } else if (event === "sms.received") {
    console.log(`📩 Inbound SMS from ${data.from}: ${data.body}`);
  }

  res.status(200).json({ received: true });
});

app.listen(4000, () => console.log("Webhook server listening on port 4000"));

Next.js App Router Webhook Receiver

// app/api/webhooks/smsnova/route.ts
import { NextResponse } from "next/server";
import crypto from "crypto";

export async function POST(req: Request) {
  const rawBody = await req.text();
  const signature = req.headers.get("x-smsnova-signature");
  const secret = process.env.SMSNOVA_WEBHOOK_SECRET || "";

  const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  if (signature !== expected) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  const payload = JSON.parse(rawBody);
  if (payload.event === "payment.received") {
    // Instant POS / ERP Reconciliation
    const { amount, trxId, senderPhone } = payload.data;
    // updateDatabase({ trxId, status: "PAID", amount });
  }

  return NextResponse.json({ success: true });
}

4. Device Dynamic Pairing Handshake

POST/devices/pair

Generates a cryptographically signed 3-minute TTL pairing token. The token is embedded into the QR code displayed on the web dashboard and scanned by the Android app.

Request Body (JSON)

{
  "orgId": "org_ullass_posnova"
}

Response (200 OK)

{
  "success": true,
  "endpoint": "https://sms.ullass.com/api",
  "pairingToken": "pair_1790300726607_2vlc8dqy",
  "orgId": "org_ullass_posnova",
  "expiresAt": 1790300906607,
  "qrDataString": "{\"v\":1,\"ep\":\"https://sms.ullass.com/api\",\"tok\":\"pair_1790300726607_2vlc8dqy\",\"org\":\"org_ullass_posnova\"}"
}