Send Phone OTP in Swift / iOS — 2026 Tutorial
Verify phone numbers in iOS apps with Swift, calling your backend that proxies the StartMessaging OTP API. Includes URLSession, async/await, and SwiftUI form examples.
StartMessaging Team
Engineering
Phone OTP is the standard way to verify users on iOS in India, especially for fintech, food delivery, and quick-commerce apps. This guide shows the SwiftUI + async/await pattern that calls your own backend, which in turn proxies the StartMessaging OTP API.
Architecture: Backend Proxy
Never call StartMessaging directly from the iOS app. The flow looks like:
iOS app ──POST /auth/send-otp──▶ Your backend ──▶ StartMessaging API
iOS app ──POST /auth/verify-otp─▶ Your backend ──▶ StartMessaging APISwift API Client
import Foundation
struct SendOtpRes: Decodable {
let requestId: String
let expiresAt: String
}
struct VerifyOtpRes: Decodable { let verified: Bool }
enum OtpError: Error { case server, decoding }
actor AuthClient {
let base = URL(string: "https://api.yourapp.com")!
func sendOtp(phone: String) async throws -> SendOtpRes {
var req = URLRequest(url: base.appendingPathComponent("auth/send-otp"))
req.httpMethod = "POST"
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
req.httpBody = try JSONSerialization.data(withJSONObject: ["phoneNumber": phone])
let (data, res) = try await URLSession.shared.data(for: req)
guard let http = res as? HTTPURLResponse, http.statusCode < 400 else { throw OtpError.server }
return try JSONDecoder().decode(SendOtpRes.self, from: data)
}
func verifyOtp(requestId: String, code: String) async throws -> Bool {
var req = URLRequest(url: base.appendingPathComponent("auth/verify-otp"))
req.httpMethod = "POST"
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
req.httpBody = try JSONSerialization.data(withJSONObject: [
"requestId": requestId, "otpCode": code
])
let (data, _) = try await URLSession.shared.data(for: req)
return try JSONDecoder().decode(VerifyOtpRes.self, from: data).verified
}
}SwiftUI Phone Login Form
struct PhoneLoginView: View {
@State private var phone = ""
@State private var requestId: String?
@State private var code = ""
let client = AuthClient()
var body: some View {
VStack {
if requestId == nil {
TextField("+91 98765 43210", text: $phone)
.keyboardType(.phonePad)
Button("Send OTP") {
Task {
let res = try await client.sendOtp(phone: phone)
requestId = res.requestId
}
}
} else {
TextField("Enter code", text: $code)
.keyboardType(.numberPad)
.textContentType(.oneTimeCode) // iOS autofill from SMS banner
Button("Verify") {
Task {
let ok = try await client.verifyOtp(
requestId: requestId!, code: code)
if ok { /* navigate */ }
}
}
}
}
}
}iOS One-Time Code Autofill
Setting .textContentType(.oneTimeCode) on the field tells iOS to suggest the code from the most recent SMS in QuickType. There is no template requirement on the SMS body — iOS recognises 4–6 digit codes automatically. For Android-side parity see OTP autofill on Android & iOS.
Best Practices
- Use App Transport Security — never plain HTTP.
- Pin your backend’s certificate or public key.
- Store the requestId only in memory, not in UserDefaults.
- Disable the Verify button until 4–6 digits are entered.
- Show a 30-second resend timer to prevent abuse.
FAQ
See pricing or read the React Native / Flutter version here.
Related Articles
Keep TRAI DLT-compliant SMS OTP on the server: React Native and Flutter clients call your API only—never ship SMS gateway keys for OTP SMS API India integrations.
Improve OTP UX with Android SMS Retriever, User Consent API, and iOS one-time code fields. Aligns with TRAI DLT-approved SMS templates and StartMessaging when your backend sends the SMS.
Send and verify SMS OTPs from a Kotlin Ktor backend or Android app using the StartMessaging API. Includes Ktor client examples, retrofit option, and Android best practices.
Ready to Send OTPs?
Integrate StartMessaging in 5 minutes. No DLT registration required.