Skip to content
SMSRay

Developer hub

Ship SMS before your coffee cools

Six REST endpoints, one auth header, one error shape. Send messages and one-time passwords from any language that can make an HTTPS request — Nepal is live today, with more countries on the way.

Base URL

https://api.smsray.com/api/sms/v1

Auth

x-api-key: <your key>

Errors

{ "error", "code", "details"? }

Quickstart

Three steps to your first message

  1. Step 1

    Get your account

    Talk to sales and the SMSRay team creates your workspace. Then sign in at app.smsray.com with a one-time code sent to your phone or email.

  2. Step 2

    Create an API key in Clients

    Open Clients, add one per app or environment, and copy the key and webhook secret — they're shown once and stored only as hashes. Your keys and live examples live in the portal.

  3. Step 3

    Send

    POST to /sms/send with the x-api-key header. You get a messageId back instantly and the status follows. Custom messages use templates approved by SMSRay; OTP uses our built-in template, so it works on day one.

Send an SMS

One POST. The reply carries the messageId, segment count and encoding.

curl https://api.smsray.com/api/sms/v1/sms/send \
  -H "x-api-key: $SMSRAY_API_KEY" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: order-1042-shipped" \
  -d '{ "to": "+9779801234567", "text": "Your order #1042 has shipped." }'

Verify a phone number

We generate the code, send it and check it. Valid 5 minutes, 5 attempts.

# 1. Send a code (6 digits, valid 5 minutes)
curl https://api.smsray.com/api/sms/v1/sms/otp/send \
  -H "x-api-key: $SMSRAY_API_KEY" -H "content-type: application/json" \
  -d '{ "to": "+9779801234567", "purpose": "login", "ip": "203.0.113.7" }'

# 2. Check what the user typed
curl https://api.smsray.com/api/sms/v1/sms/otp/verify \
  -H "x-api-key: $SMSRAY_API_KEY" -H "content-type: application/json" \
  -d '{ "to": "+9779801234567", "purpose": "login", "code": "482913" }'
MethodPathWhat it does
POST/sms/sendSend a transactional, OTP or promotional (Enterprise) message
POST/sms/otp/sendGenerate and send a 6-digit code
POST/sms/otp/verifyCheck the code your user typed
GET/sms/messages/:idLook up a message and its status
GET/sms/balanceWorkspace balance, rates and your counters
GET/sms/coverageWhere this key can send, and where SMSRay delivers

Webhooks

Verify every event in a few lines

Each delivery carries x-lacspace-signature: t=…,v1=… — an HMAC-SHA256 of the timestamp and raw body with your webhook secret.

  • Events: message.status and message.inbound
  • Reject timestamps outside ±300 s to stop replays
  • During secret rotation two v1 values are sent — accept either
  • 8 attempts with backoff: 10 s, 30 s, 2 min, 10 min, 30 min, 1 h, 3 h, 6 h
  • Respond 2xx within 10 s; use x-lacspace-delivery to de-duplicate
Webhook reference
import crypto from "node:crypto";

// x-lacspace-signature: t=<unix>,v1=<hex>[,v1=<hex during secret rotation>]
export function verifySmsrayWebhook(rawBody, header, secret, toleranceSec = 300) {
  const pairs = header.split(",").map((p) => p.split("="));
  const t = Number(pairs.find(([k]) => k === "t")?.[1]);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = Buffer.from(
    crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"), "hex");
  return pairs
    .filter(([k]) => k === "v1")
    .some(([, v]) => {
      const sig = Buffer.from(v, "hex");
      return sig.length === expected.length && crypto.timingSafeEqual(sig, expected);
    });
}

Testing tips

Build it right the first time

Use test mode, then your own number

Put a key in test mode while you integrate: messages are recorded, never sent, and free. Then send a real OTP to your own number in E.164 (+9779801234567) and watch it move in the dashboard.

Always send an Idempotency-Key

Use something stable like order-1042-shipped. If your job retries, the replay returns the original response with Idempotent-Replayed: true — no second SMS, no second charge.

Poll status while you build

Before your webhook endpoint is live, call GET /sms/messages/:id to follow a message through queued, sent and delivered. Switch to webhooks in production.

Treat a wrong OTP as data

A wrong code returns 200 with verified: false and a reason. Pass the end user's ip on send to turn on per-IP limits.

SDKs

REST-first. Official libraries coming soon.

Today SMSRay is a plain JSON-over-HTTPS API, so it works from any stack with no package to install. The docs show cURL, Node.js, Python, PHP and Go for every endpoint, and the OpenAPI 3.1 file lets you generate a client for anything else.

cURLNode.jsPythonPHPGoOpenAPI 3.1SDKs · soon

Specs and PDF

Generated from the docs, so they always match.

What's new

10 Oct 2026: templates library, GET /sms/coverage, E.164 numbers and 422 destination_not_supported.

Read the changelog

Developer support

Send the messageId and error code, never your full key.

Get your API key

Talk to sales and we set up your workspace. Sign in, add a client and send your first message from the terminal. Plans from NPR 3,000 a month.

Already a customer? Log in