Consider the Verify API first. Telnyx offers a dedicated Verify API that handles OTP generation, delivery, and verification for you — including retry logic, rate limiting, and multi-channel support (SMS, voice, WhatsApp). Use this guide only if you need full control over the 2FA flow.
How SMS 2FA works
Generate and send an OTP
Generate a cryptographically secure OTP and send it via SMS:Security best practices
Use cryptographically secure random generation
Use cryptographically secure random generation
Never use
Math.random(), rand(), or similar non-cryptographic functions for OTP generation. Use:| Language | Secure Function |
|---|---|
| Python | secrets.choice() or secrets.token_hex() |
| Node | crypto.randomBytes() |
| Ruby | SecureRandom.random_number() |
| Go | crypto/rand.Int() |
| Java | SecureRandom.nextInt() |
| .NET | RandomNumberGenerator.GetBytes() |
| PHP | random_int() |
Set expiry times
Set expiry times
OTPs should expire after a short window (3-5 minutes is typical). Never allow OTPs to be valid indefinitely.
- Store the expiry timestamp alongside the OTP
- Check expiry before validating
- Delete expired OTPs proactively
Limit verification attempts
Limit verification attempts
Allow a maximum of 3 verification attempts per OTP. After exceeding the limit, invalidate the OTP and require the user to request a new one. This prevents brute-force attacks on short numeric codes.
Use constant-time comparison
Use constant-time comparison
Always use constant-time string comparison when verifying OTPs to prevent timing attacks:
| Language | Function |
|---|---|
| Python | secrets.compare_digest() |
| Node | crypto.timingSafeEqual() |
| Go | subtle.ConstantTimeCompare() |
| Java | MessageDigest.isEqual() |
| .NET | CryptographicOperations.FixedTimeEquals() |
| PHP | hash_equals() |
Rate limit OTP requests
Rate limit OTP requests
Prevent abuse by limiting how frequently a user can request new OTPs:
- Per phone number: Maximum 1 OTP request per 60 seconds
- Per IP address: Maximum 10 OTP requests per hour
- Per account: Maximum 5 OTP requests per hour
Use numeric-only codes
Use numeric-only codes
Use numeric-only OTPs (e.g.,
847291) rather than alphanumeric codes. They are:- Easier for users to type on mobile
- Compatible with SMS autofill on iOS and Android
- Sufficient security when combined with attempt limits and expiry
Support SMS autofill
Support SMS autofill
iOS and Android can automatically detect and fill OTP codes from SMS. To enable this:Android (SMS Retriever API):
Include your app’s hash at the end of the message:iOS:
iOS automatically detects codes from messages containing “code” or “passcode.” No special formatting needed, but keeping the OTP on its own line helps.
When to use the Verify API instead
The Telnyx Verify API is a better choice when:| Requirement | DIY (this guide) | Verify API |
|---|---|---|
| OTP generation & storage | You build it | Handled for you |
| Retry logic | You build it | Built-in |
| Rate limiting | You build it | Built-in |
| Multi-channel (SMS + Voice + WhatsApp) | Separate implementation | Single API |
| Delivery status tracking | Manual webhook handling | Built-in |
| Compliance & audit logging | You build it | Built-in |