310 lines
9.3 KiB
Markdown
310 lines
9.3 KiB
Markdown
# 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` |
|