urmi-ai/OTP_SMS_IMPLEMENTATION.md
2026-10-02 11:22:40 +05:30

310 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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` |