first commit
This commit is contained in:
commit
efa5080897
127 changed files with 18610 additions and 0 deletions
310
OTP_SMS_IMPLEMENTATION.md
Normal file
310
OTP_SMS_IMPLEMENTATION.md
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
# 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` |
|
||||
Loading…
Add table
Add a link
Reference in a new issue