Most OTP examples stop too early.
They show how to generate a code, store it somewhere, and send an SMS through a provider that hides routing behind a black box.
That works for demos.
It breaks down when routing, pricing and delivery behavior matter.
At BridgeXAPI, messaging is approached differently.
Routing is not hidden.
It is part of the application.
To demonstrate that, we built a FastAPI OTP service on top of programmable SMS routing.
This is not just an OTP example
This is an example of what authentication flows look like when routing, pricing and delivery are part of your system — not abstracted away by a provider.
The routing model
Most messaging APIs follow this pattern:
- you define the message
- the provider selects the route
- pricing and delivery behavior are opaque
In this model, routing is a black box.
BridgeXAPI follows a different approach:
- the developer selects the route (
route_id) - pricing is tied to that route
- delivery behavior becomes predictable
- routing decisions live in application code
This matters for OTP systems.
A login code is not the same as bulk messaging.
The delivery path should be explicit.
The FastAPI service
The service exposes two endpoints:
-
POST /send-otp -
POST /verify-otp
Example request:
{
"phone_number": "31651860670",
"purpose": "login",
"route_id": 3
}
Example response:
{
"status": "otp_sent",
"phone_number": "*******0670",
"purpose": "login",
"route_id": 3,
"expires_in_seconds": 300,
"cooldown_seconds": 45,
"debug_code": "816369",
"bx_message_id": "BX-22229-1d5e5779b1904695",
"order_id": "22229",
"delivery_status_url": "https://hi.bridgexapi.io/api/v1/dlr/BX-22229-1d5e5779b1904695"
}
The important part is not just "OTP sent".
The response includes:
-
bx_message_id -
order_id -
delivery_status_url
These are infrastructure-level identifiers.
They allow the application to track delivery state, not just submission.
Security and flow
The service implements:
- OTP hashing using HMAC
- expiration (TTL)
- resend cooldown
- attempt limits
- masked phone numbers in responses
For development, a debug mode can expose the OTP code in responses.
This should be disabled in production.
Storage model
This example uses an in-memory store.
The goal is to keep the flow clear:
- generate OTP
- send via explicit route
- store hashed state
- verify
In production, this would be replaced with Redis or another persistent backend.
The API contract remains the same.
Why this approach
Most OTP implementations treat messaging as a side-effect.
This implementation treats messaging as part of the system design.
Routing is explicit.
Delivery is observable.
Pricing is predictable.
The application owns these decisions.
GitHub
https://github.com/bridgexapi-dev/bridgexapi-fastapi-otp
Top comments (1)
This is part of a broader direction: exposing messaging infrastructure instead of hiding it behind APIs.