# Ultronsms — URMI OTP flow (hardcoded) Hardcoded integration for sending **URMI app OTP verification SMS** via [Ultronsms](https://ultronsms.com). Parameters below were verified with live sends and delivery reports on **2026-08-30**. --- ## Overview ``` ┌─────────────┐ generate OTP ┌──────────────┐ SendSMS API ┌─────────────┐ │ Your app │ ────────────────────► │ SMS client │ ──────────────────► │ Ultronsms │ │ (backend) │ │ (this doc) │ │ gateway │ └─────────────┘ └──────────────┘ └──────┬──────┘ │ │ │ │ │ GetDelivery API │ │ ◄──────────────────────────────────────┘ │ │ ▼ ▼ Store OTP + expiry Delivered / Failed ``` **Flow** 1. Generate a numeric OTP (e.g. 6 digits). 2. Call `SendSMS` with the DLT-approved template text (OTP + validity substituted). 3. Store OTP server-side with expiry; return success/failure to the client (never return the OTP in API responses if SMS is the only channel). 4. Optionally poll `GetDelivery` with the returned `JobId` for delivery confirmation. --- ## Hardcoded configuration These values are fixed for the URMI OTP use case. | Setting | Value | |---------|-------| | **Base URL** | `https://ultronsms.com` | | **Username** | `GENZENITH` | | **Password** | `dssdl@33` | | **Sender ID** | `URMIAI` | | **Channel** | `Trans` (Transactional) | | **Route** | `2` | | **PE ID** (`peid`) | `1701178780498045011` | | **DLT Template ID** (`dlttemplateid`) | `1777178797989033323` | | **DCS** | `0` (English) | | **Flash SMS** | `0` (normal SMS) | ### DLT-approved template **Registered text** (placeholders `{#num#}` × 2): ``` Your URMI app verification code is {#num#}. It is valid for {#num#} minutes. Do not share it to anyone - URMI AI ``` **Sent text** (example — replace variables only): ``` Your URMI app verification code is 482916. It is valid for 10 minutes. Do not share it to anyone - URMI AI ``` The message body must match the approved template character-for-character except for the variable values. --- ## API endpoints | Action | Method | URL | |--------|--------|-----| | Send SMS | `GET` | `https://ultronsms.com/api/mt/SendSMS` | | Delivery status | `GET` | `https://ultronsms.com/api/mt/GetDelivery` | Always use **HTTPS**. HTTP returns `301 Moved Permanently`. ### SendSMS query parameters | Parameter | Required | OTP value | |-----------|----------|-----------| | `user` | yes | `GENZENITH` | | `password` | yes | `dssdl@33` | | `senderid` | yes | `URMIAI` | | `channel` | yes | `Trans` | | `route` | yes | `2` | | `number` | yes | E.164 without `+`, e.g. `916296346452` | | `text` | yes | Filled DLT template (see above) | | `peid` | yes | `1701178780498045011` | | `dlttemplateid` | yes | `1777178797989033323` | | `DCS` | yes | `0` | | `flashsms` | yes | `0` | ### GetDelivery query parameters | Parameter | Required | Description | |-----------|----------|-------------| | `user` | yes | Same as SendSMS | | `password` | yes | Same as SendSMS | | `jobid` | yes | `JobId` from SendSMS response | --- ## Response reference ### SendSMS — success ```json { "ErrorCode": "000", "ErrorMessage": "Done", "JobId": "211261464", "MessageData": [ { "Number": "916296346452", "MessageId": "X4jHLUsSDUSu6cD3yodiYw", "Message": null } ] } ``` ### SendSMS — common errors | ErrorCode | ErrorMessage | Cause | |-----------|--------------|-------| | `7` | username or password is invalid | Wrong credentials | | `25` | NullReferenceException (server) | Invalid `route` (e.g. UI label `T_22` instead of `2`) | ### GetDelivery — success ```json { "ErrorCode": "0", "ErrorMessage": "Done", "DeliveryReports": [ { "MessageId": "X4jHLUsSDUSu6cD3yodiYw", "DeliveryStatus": "Delivered", "DeliveryDate": "2026-08-30T11:37:18.523", "ErrorCode": "000" } ] } ``` `DeliveryStatus` may be `Sent` briefly before updating to `Delivered`. Poll again after 3–5 seconds if needed. --- ## Python implementation (hardcoded) ```python import random import time from urllib.parse import urlencode from urllib.request import urlopen # --- Hardcoded URMI OTP config --- ULTRONSMS_BASE = "https://ultronsms.com" ULTRONSMS_USER = "GENZENITH" ULTRONSMS_PASSWORD = "dssdl@33" SENDER_ID = "URMIAI" PE_ID = "1701178780498045011" DLT_TEMPLATE_ID = "1777178797989033323" OTP_VALIDITY_MINUTES = 10 def _get_json(url: str) -> dict: with urlopen(url, timeout=30) as resp: import json return json.loads(resp.read().decode()) def _build_url(path: str, params: dict) -> str: return f"{ULTRONSMS_BASE}{path}?{urlencode(params)}" def generate_otp(length: int = 6) -> str: return "".join(str(random.randint(0, 9)) for _ in range(length)) def format_otp_message(otp: str, validity_minutes: int = OTP_VALIDITY_MINUTES) -> str: return ( f"Your URMI app verification code is {otp}. " f"It is valid for {validity_minutes} minutes. " f"Do not share it to anyone - URMI AI" ) def normalize_phone(number: str) -> str: """Convert +91XXXXXXXXXX or 91XXXXXXXXXX to gateway format.""" digits = "".join(c for c in number if c.isdigit()) if len(digits) == 10: return f"91{digits}" return digits def send_urmi_otp(mobile: str, otp: str | None = None) -> dict: """ Send URMI OTP SMS. Returns gateway response including JobId. If otp is None, generates a new 6-digit code. """ code = otp or generate_otp() params = { "user": ULTRONSMS_USER, "password": ULTRONSMS_PASSWORD, "senderid": SENDER_ID, "channel": "Trans", "route": "2", "DCS": "0", "flashsms": "0", "number": normalize_phone(mobile), "text": format_otp_message(code), "peid": PE_ID, "dlttemplateid": DLT_TEMPLATE_ID, } result = _get_json(_build_url("/api/mt/SendSMS", params)) result["_otp"] = code # for app storage; do not expose to client return result def get_delivery(job_id: str) -> dict: params = { "user": ULTRONSMS_USER, "password": ULTRONSMS_PASSWORD, "jobid": job_id, } return _get_json(_build_url("/api/mt/GetDelivery", params)) def send_otp_and_wait_delivery(mobile: str, max_wait_seconds: int = 15) -> dict: """Send OTP and poll delivery until Delivered or timeout.""" send_result = send_urmi_otp(mobile) if send_result.get("ErrorCode") != "000": return {"send": send_result, "delivery": None, "otp": send_result.get("_otp")} job_id = send_result["JobId"] deadline = time.time() + max_wait_seconds delivery = None while time.time() < deadline: delivery = get_delivery(job_id) reports = delivery.get("DeliveryReports") or [] if reports and reports[0].get("DeliveryStatus") == "Delivered": break time.sleep(3) return { "send": send_result, "delivery": delivery, "otp": send_result["_otp"], } ``` ### Usage ```python result = send_otp_and_wait_delivery("+916296346452") otp = result["otp"] # store in Redis/DB with expiry job_id = result["send"]["JobId"] ``` --- ## cURL (quick test) **Send OTP** ```bash curl -sS -L -G "https://ultronsms.com/api/mt/SendSMS" \ --data-urlencode "user=GENZENITH" \ --data-urlencode "password=dssdl@33" \ --data-urlencode "senderid=URMIAI" \ --data-urlencode "channel=Trans" \ --data-urlencode "DCS=0" \ --data-urlencode "flashsms=0" \ --data-urlencode "number=916296346452" \ --data-urlencode "text=Your URMI app verification code is 482916. It is valid for 10 minutes. Do not share it to anyone - URMI AI" \ --data-urlencode "route=2" \ --data-urlencode "peid=1701178780498045011" \ --data-urlencode "dlttemplateid=1777178797989033323" ``` **Check delivery** ```bash curl -sS -L -G "https://ultronsms.com/api/mt/GetDelivery" \ --data-urlencode "user=GENZENITH" \ --data-urlencode "password=dssdl@33" \ --data-urlencode "jobid=211261464" ``` --- ## Application integration checklist - [ ] Generate OTP server-side; store with TTL matching template validity (10 minutes). - [ ] Rate-limit OTP requests per phone number and IP. - [ ] Do not log full OTP or password in application logs. - [ ] Verify user-submitted OTP against stored value before marking session verified. - [ ] Handle `ErrorCode != 000` with a generic user message ("Unable to send OTP, try again"). - [ ] Use demo account (`GENZENITHDemo`) only in non-production environments. --- ## Verified test (2026-08-30) | Field | Value | |-------|-------| | Number | `+91 6296346452` | | Job ID | `211261464` | | Message ID | `X4jHLUsSDUSu6cD3yodiYw` | | Delivery | **Delivered** at `2026-08-30T11:37:18.523` |