NEXO ROUTE
Guide

Send OTP from
Python

Send an OTP from Python in 10 lines using the requests library. Async variant + Flask webhook receiver included.

Get an API key Full docs

1. Install

pip install requests
# For async:
pip install httpx
# For webhook receiver:
pip install flask

2. 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 data

3. 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

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.

Switch from

Use cases

SDK guides