Connect with the Backend
Integrating the UI with the Backend Endpoints
In the previous module we built the UI and a small UI state machine (VerifyUiState). Now we’ll connect that UI to our backend.
The main idea is that the Android app will never call Vonage directly. It will only call your backend:
POST /verificationto start the verification flowPOST /check-codeto validate a code (SMS code for now)POST /nextto force the fallback channel (SMS) immediately (so we don’t wait ~20 seconds)
Let’s start with the implementation!
Start verification (POST /verification)
When the user taps Start verification:
The app sends the phone number to
/verificationThe backend returns:
request_id(always)check_url(optional, used later for Silent Auth)
In this section, we will skip Silent Auth and immediately request fallback to SMS by calling
/nextUI transitions to the SMS screen (
EnterSms(requestId))
private suspend fun startVerification(phone: String): Pair<String, String?> = withContext(Dispatchers.IO) {
val client = OkHttpClient()
val json = Gson().toJson(mapOf("phone" to phone))
val requestBody = json.toRequestBody("application/json".toMediaType())
val request = Request.Builder()
.url("$BACKEND_URL/verification")
.post(requestBody)
.build()
val response = client.newCall(request).execute()
if (!response.isSuccessful) {
val errorBody = response.body?.string() ?: "Unknown error"
throw IOException("Start verification failed: HTTP ${response.code} - $errorBody")
}
val body = response.body?.string() ?: throw IOException("Empty response body")
val jsonBody = Gson().fromJson(body, JsonObject::class.java)
val requestId = jsonBody.get("request_id")?.asString ?: throw IOException("Missing request_id")
val checkUrl = jsonBody.get("check_url")?.asString // may be null
Pair(requestId, checkUrl)
}
Submit SMS code (POST /check-code)
When the user taps Submit code:
- The app sends
{ request_id, code }to/check-code - The backend responds with
{ verified: true/false } - UI shows success or “invalid code”
private suspend fun submitCode(requestId: String, code: String): CheckCodeResponse = withContext(Dispatchers.IO) {
val client = OkHttpClient()
val json = Gson().toJson(mapOf("request_id" to requestId, "code" to code))
val requestBody = json.toRequestBody("application/json".toMediaType())
val request = Request.Builder()
.url("$BACKEND_URL/check-code")
.post(requestBody)
.build()
val response = client.newCall(request).execute()
if (!response.isSuccessful) {
val errorBody = response.body?.string() ?: "Unknown error"
throw IOException("Check code failed: HTTP ${response.code} - $errorBody")
}
val body = response.body?.string() ?: throw IOException("Empty response body")
val jsonBody = Gson().fromJson(body, JsonObject::class.java)
CheckCodeResponse(
verified = jsonBody.get("verified")?.asBoolean ?: false,
status = jsonBody.get("status")?.asString
)
}
Force fallback quickly (POST /next)
Calling /next is optional from a “correctness” standpoint (Vonage can fall back automatically), but it’s very useful for UX:
If we already know we won’t complete Silent Auth (or we’re not implementing it yet), calling /next avoids waiting for the Silent Auth timeout.
private suspend fun requestNextWorkflow(requestId: String): Unit = withContext(Dispatchers.IO) {
val client = OkHttpClient()
val json = Gson().toJson(mapOf("requestId" to requestId))
val requestBody = json.toRequestBody("application/json".toMediaType())
val request = Request.Builder()
.url("$BACKEND_URL/next")
.post(requestBody)
.build()
val response = client.newCall(request).execute()
if (!response.isSuccessful) {
val errorBody = response.body?.string() ?: "Unknown error"
throw IOException("Next workflow failed: HTTP ${response.code} - $errorBody")
}
}
Getting Started with Silent Authentication
Silent Authentication takes quite a bit to understand. This tutorial shows you how to build an integration from scratch with Nodejs and Kotlin