How to Send OTP in Flask (Python) — 2026 Tutorial
Send and verify SMS OTPs from a Flask application using the StartMessaging API. Includes app factory, blueprint routes, sessions, and error handling.
StartMessaging Team
Engineering
Flask is the go-to micro-framework for small Python services. This guide shows how to drop a phone-OTP login on top of Flask using the StartMessaging OTP API in fewer than 80 lines of code.
Prerequisites
- Python 3.10+ and Flask 3.x.
- A free StartMessaging account and an API key.
pip install Flask requests python-dotenv Flask-LimiterApp Factory Setup
# app/__init__.py
import os
from flask import Flask
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(get_remote_address, default_limits=["60 per minute"])
def create_app():
app = Flask(__name__)
app.secret_key = os.environ["FLASK_SECRET"]
limiter.init_app(app)
from .auth import bp as auth_bp
app.register_blueprint(auth_bp, url_prefix="/auth")
return appOTP Service
# app/services/otp.py
import os, uuid, requests
BASE = "https://api.startmessaging.com"
HEAD = {
"Content-Type": "application/json",
"X-API-Key": os.environ["STARTMESSAGING_API_KEY"],
}
def send_otp(phone):
res = requests.post(
f"{BASE}/otp/send",
json={"phoneNumber": phone, "idempotencyKey": str(uuid.uuid4())},
headers=HEAD, timeout=10,
)
res.raise_for_status()
return res.json()["data"]
def verify_otp(request_id, code):
res = requests.post(
f"{BASE}/otp/verify",
json={"requestId": request_id, "otpCode": code},
headers=HEAD, timeout=10,
)
return res.ok and res.json()["data"]["verified"]Auth Blueprint
# app/auth.py
from flask import Blueprint, request, session, jsonify
from .services.otp import send_otp, verify_otp
from . import limiter
bp = Blueprint("auth", __name__)
@bp.post("/send-otp")
@limiter.limit("5 per hour", key_func=lambda: request.json.get("phone", ""))
def send():
phone = request.json.get("phone", "")
try:
data = send_otp(phone)
except Exception as e:
return jsonify(error=str(e)), 502
session["otp_request_id"] = data["requestId"]
return jsonify(expires_at=data["expiresAt"])
@bp.post("/verify-otp")
def verify():
code = request.json.get("code", "")
rid = session.pop("otp_request_id", None)
if not rid:
return jsonify(error="session expired"), 400
if not verify_otp(rid, code):
return jsonify(verified=False), 401
session["user_phone"] = request.json.get("phone")
return jsonify(verified=True)Sessions and Cookies
Flask’s built-in cookie session is signed but not encrypted, so only store opaque values like the requestId — never the OTP itself. For higher-security apps, use server-side sessions via Flask-Session backed by Redis.
Best Practices
- Use Flask-Limiter on the send endpoint, keyed by phone.
- Always set an HTTP timeout on the requests call.
- Never log
request.jsonon the verify endpoint. - For background sends, queue them via Celery or RQ so the request returns instantly.
FAQ
See the equivalent Django guide if you prefer DRF, or check our OTP flow design article.
Related Articles
Send and verify SMS OTPs from Django and Django REST Framework using the StartMessaging API. Includes a service module, DRF views, serializers, and rate limiting.
Python tutorial to send and verify OTP via SMS using the requests library and StartMessaging API. Includes Flask and Django integration examples.
Learn what idempotency keys are, why they matter for OTP APIs, and how to implement them correctly to prevent duplicate SMS charges and improve reliability.
Ready to Send OTPs?
Integrate StartMessaging in 5 minutes. No DLT registration required.