Send OTP from
Python
Send an OTP from Python in 10 lines using the requests library. Async variant + Flask webhook receiver included.
1. Install
pip install requests
# For async:
pip install httpx
# For webhook receiver:
pip install flask2. Send your first OTP
Set NEXO_KEY in your environment, then run:
# send_otp.py
import os
import requests
API_KEY = os.environ["NEXO_KEY"]
def send_otp(phone: str, code: str) -> dict:
res = requests.post(
"https://nexoroute.dev/api/v1/send-otp",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
},
json={"phone": phone, "code": code},
timeout=15
)
data = res.json()
if not data.get("ok"):
# Common errors: insufficient_balance, invalid_phone, no_route, ip_not_allowed
raise RuntimeError(f"NEXO send failed: {data.get('error')}")
return data
# Usage
result = send_otp("+84981234567", "482917")
print(f"Dispatched id={result['id']} via {result['carrier']}")
print(f"Charged ${result['cost']}, wallet balance ${result['balance']}")
# --- Async variant with httpx ---
import httpx
async def send_otp_async(phone: str, code: str) -> dict:
async with httpx.AsyncClient() as client:
res = await client.post(
"https://nexoroute.dev/api/v1/send-otp",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
},
json={"phone": phone, "code": code},
timeout=15.0
)
data = res.json()
if not data.get("ok"):
raise RuntimeError(f"NEXO send failed: {data.get('error')}")
return data3. Receive delivery webhooks
We POST a signed event to your URL when each OTP reaches a terminal state (delivered or failed). Verify the HMAC before trusting the payload.
# webhook_receiver.py (Flask)
import hashlib
import hmac
import os
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = os.environ["NEXO_WEBHOOK_SECRET"]
SECRET_KEY = hashlib.sha256(WEBHOOK_SECRET.encode()).digest()
@app.post("/webhooks/nexo")
def webhook():
raw = request.get_data()
sig = request.headers.get("X-NEXO-Signature", "")
expected = "sha256=" + hmac.new(SECRET_KEY, raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected):
abort(401)
event = request.get_json()
print(f"OTP {event['id']} → {event['status']}")
# event = { type, id, phone, carrier, status, cost, error_code, created_at, completed_at }
return "ok", 200
if __name__ == "__main__":
app.run(port=3000)Common pitfalls
- Use timeout= on every requests call — default is no timeout, can hang forever
- hmac.compare_digest is constant-time — never use == for signature comparison
- Use request.get_data() before request.get_json() — once parsed, the raw body is gone
- Pin requests version in production (security advisories occasionally land)
- For Django, use @csrf_exempt + accept the raw body via request.body in the view
FAQ
Do you have an official PyPI package?
Not yet. The 10-line wrapper above is usually enough. We may publish nexoroute on PyPI when the API matures.
How do I use this in Django?
Wrap send_otp() in a service module, call from your view. The webhook handler needs @csrf_exempt and should read request.body directly for HMAC verification.
Can I use this with FastAPI?
Yes — use the async send_otp_async variant. FastAPI webhook receivers can use Request.body() to get raw bytes for HMAC.
What about Celery / async task queues?
Send the OTP from your task. Webhook callback updates a DB row that your app polls. We do not have native task queue integration.
Ship OTP in 7 lines of Python
5 free test OTPs on signup. Real Vietnamese carriers, real delivery.
Use cases