Call verification banner
iOS gives us a clean win here: CXCallObserver reports active calls without requiring any permission prompt. Verification is therefore frictionless on iOS — the user never sees a permission dialog.
How it works
- The SDK creates a
CXCallObserverat init time - When a call becomes active,
Relavoi.shared.verification.isCallActiveflips totrue - When your app comes to the foreground, call
Relavoi.shared.verification.verify(userPhone:) - The SDK hashes the phone with the tenant salt server-side and hits
GET /v1/sessions/verify - You receive a
VerificationResultand render the appropriate banner
Basic usage
import SwiftUI
import RelavoiSDK
struct HomeView: View {
@State private var result: VerificationResult?
let currentUserPhone: String
var body: some View {
VStack {
if let result { BannerView(result: result) }
Text("Home")
}
.task {
if Relavoi.shared.verification.isCallActive {
result = try? await Relavoi.shared.verification.verify(userPhone: currentUserPhone)
}
}
}
}
isCallActive reflects the current CXCallObserver state synchronously, so you can gate the network call on it. There is no separate call-state callback API — check isCallActive when your view appears or when the app returns to the foreground.
Banner UI
VerificationResult is a struct with a verified flag. Branch on it:
struct BannerView: View {
let result: VerificationResult
var body: some View {
if result.verified {
Label(result.context ?? "Verified call", systemImage: "checkmark.shield.fill")
.padding()
.background(Color.green.opacity(0.2))
.foregroundStyle(.green)
.clipShape(RoundedRectangle(cornerRadius: 12))
} else {
Label("Warning: this call is not verified", systemImage: "exclamationmark.triangle.fill")
.padding()
.background(Color.red.opacity(0.2))
.foregroundStyle(.red)
.clipShape(RoundedRectangle(cornerRadius: 12))
}
}
}
:::note context is not populated yet
context is reserved for a human-readable string like "Your Chowdeck rider is calling", but the backend does not populate it today — it is always nil. Always provide a fallback label as shown above.
:::
VerificationResult struct
public struct VerificationResult: Decodable, Equatable {
public let verified: Bool
public let context: String?
public let sessionId: String?
public let expiresAt: Date?
}
When verified == true, sessionId identifies the matched session and expiresAt is when it stops being callable. When verified == false, these are typically nil.
Why no permission?
CXCallObserver is part of CallKit and Apple intentionally exposes it without a permission prompt — Apple considers passive call-state observation by an app whose user is in an active call to be safe. This is why iOS gets the native, Revolut-style UX: no dialogs, just clean detection. (On Android the equivalent requires the READ_PHONE_STATE runtime permission.)
:::tip Pair with Live Activities Combine verification with Live Activities to keep the verified banner pinned to the Lock Screen and Dynamic Island for the duration of the call. End users find it dramatically more reassuring. :::