Hawcx Android SDK (V6)
Integrate Hawcx V6 on Android with an adaptive authentication flow, MFA, redirects, and trusted-device support.
Overview
Hawcx V6 for Android gives you a prompt-driven authentication flow that adapts to the authentication methods configured for your project. Your app starts a flow, renders the next prompt Hawcx returns, and completes sign-in by exchanging the resulting authorization code on your backend.
This is the recommended Android integration path for new projects using Hawcx V6.
V6 docs, current SDK API names
V6 is the public SDK release name. The current Android API surface uses
HawcxV1SDK and related HawcxV1* types because the SDK is built on Hawcx's
protocol v1 flow.
Already using the existing V5 SDK?
If your app is already integrated with the existing HawcxSDK V4/V5 flow, continue
using the V5 Android guide. This page is
for the new V6 integration path only.
Which Guide Should You Use?
Use this V6 guide if:
- you are starting a new Android integration
- you want an adaptive flow with backend-selected primary, MFA, and trusted-device steps
- you are implementing the current Hawcx Android release path in your app
Use the V5 Android guide if:
- your app already ships the existing
HawcxSDKV4/V5 integration - you still depend on the older
authenticateV5/submitOtpV5or V4 flows - you are maintaining an existing production integration and are not ready to migrate yet
Incremental migration is supported
If you are migrating gradually, HawcxV1SDK can own the new V6 auth flow while
HawcxSDK continues to power existing utilities such as V4/V5 auth flows,
push approvals, device sessions, or older token helpers.
Before You Begin
Before integrating the Android SDK, make sure your Hawcx project is configured in the Hawcx dashboard:
- Create or select your project
- Configure the primary authentication step
- Configure MFA and trusted-device behavior if needed
- Generate a Config ID for the SDK
For product configuration details, see:
How the V6 Flow Works
- Your app initializes
HawcxSDKwith your tenant host and obtains aHawcxV1SDKwrapper for the V6 flow. - You start the flow with a
HawcxV1FlowType, usuallySIGNIN, plus the user's identifier. - The backend responds with the next step for that user and project configuration.
- Your app renders the current prompt:
- choose a method
- enter an OTP
- set up SMS
- set up TOTP
- continue a redirect
- wait for approval
- When the flow completes, the SDK returns
authCodeand, when applicable,codeVerifier. - Your backend exchanges the code and creates the user session.
Integration Responsibilities
Your app is responsible for:
- collecting the user's identifier
- rendering the current prompt
- sending the user's input back to the SDK
- handling redirects when prompted
- sending the final authorization code to your backend
The SDK handles:
- protocol requests and required headers
- PKCE generation when needed
- trusted-device storage and secure device credentials
- device-trust processing
- approval polling when the flow requires it
Your backend handles:
- exchanging the authorization code
- verifying returned claims
- creating the app session
- returning the result your app needs to continue
What the SDK handles automatically
The SDK manages the protocol details for you, including required headers, PKCE when needed, secure trusted-device storage, and device-trust processing.
Requirements
- Android API 26+
- A Kotlin- or Java-based Android app
- A Hawcx Config ID for your project
- Your Hawcx tenant base URL
Example base URL:
https://stage-api.hawcx.comFor the recommended Android integration path, pass the tenant host root only. The SDK
derives the correct V4/V5 and V6 routes from that host. Do not append /v1, /auth,
or /hc_auth yourself.
Install
Gradle
Add the Hawcx Maven repository:
repositories {
maven {
url = uri("https://raw.githubusercontent.com/hawcx/hawcx_android_sdk/main/maven")
metadataSources {
mavenPom()
artifact()
}
}
google()
mavenCentral()
}Then add the dependency:
dependencies {
implementation "api.hawcx:hawcx:6.0.2"
}Runtime dependencies such as OkHttp, Retrofit, Coroutines, Gson, and AndroidX helpers are declared in the published POM and are pulled in automatically.
Initialize the SDK
import com.hawcx.internal.HawcxSDK
val hawcxCore = HawcxSDK(
context = applicationContext,
projectApiKey = "<YOUR_CONFIG_ID>",
baseUrl = "https://stage-api.hawcx.com"
)
val hawcx = hawcxCore.buildProtocolV1Sdk(
configId = "<YOUR_CONFIG_ID>",
autoPollApprovals = true
)Initialization Notes
HawcxSDKis still the Android entry point because it wires the Android-specific context, secure storage, device info, and device-trust helpers used by the V6 flow.buildProtocolV1Sdk(...)returns theHawcxV1SDKwrapper that powers the V6 prompt-driven flow.configIdis the Hawcx value provisioned for this integration. In current public releases, this is the same value you may receive as your project API key / Config ID.baseUrlshould point to your Hawcx tenant host root. When you initialize Android throughHawcxSDK(...), let the SDK derive the legacy and protocol routes from that host.autoPollApprovalsdefaults totrue, which is the right choice for most apps.relyingPartyis optional. Set it only when your backend expects anX-Relying-Partyheader for this integration.- If your app still uses the older
HawcxSDKsurface, you can keep both SDK paths in the same app during migration.
Build an Auth Coordinator
The recommended integration shape is to keep one coordinator or view model responsible for:
- holding the current
HawcxV1FlowUpdate - calling
start(...) - reacting to
flow.onUpdate - forwarding user input back to the SDK
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import com.hawcx.internal.HawcxSDK
import com.hawcx.protocol.v1.HawcxV1FlowType
import com.hawcx.protocol.v1.HawcxV1FlowUpdate
class AuthViewModel(application: Application) : AndroidViewModel(application) {
private val hawcxCore = HawcxSDK(
context = application.applicationContext,
projectApiKey = "<YOUR_CONFIG_ID>",
baseUrl = "https://stage-api.hawcx.com"
)
private val sdk = hawcxCore.buildProtocolV1Sdk(
configId = "<YOUR_CONFIG_ID>",
autoPollApprovals = true
)
private val _update = MutableStateFlow<HawcxV1FlowUpdate>(HawcxV1FlowUpdate.Idle)
val update: StateFlow<HawcxV1FlowUpdate> = _update.asStateFlow()
init {
sdk.flow.onUpdate = { nextUpdate ->
_update.value = nextUpdate
}
}
fun start(identifier: String) {
sdk.start(
flowType = HawcxV1FlowType.SIGNIN,
identifier = identifier
)
}
fun selectMethod(methodId: String) {
sdk.flow.selectMethod(methodId)
}
fun submitCode(code: String) {
sdk.flow.submitCode(code)
}
fun submitTotp(code: String) {
sdk.flow.submitTotp(code)
}
fun submitPhone(phone: String) {
sdk.flow.submitPhone(phone)
}
fun resend(): Boolean {
return sdk.flow.resend()
}
fun cancel() {
sdk.flow.cancel()
}
fun reset() {
sdk.reset()
}
}Start Authentication
HawcxV1FlowType supports:
SIGNINSIGNUPACCOUNT_MANAGE
Most apps should start with SIGNIN:
viewModel.start(identifier = "[email protected]")Use additional fields only when your tenant policy requires them:
sdk.start(
flowType = HawcxV1FlowType.SIGNIN,
identifier = "[email protected]",
startToken = "<optional-start-token>",
inviteCode = "12345678"
)startToken is optional and is only needed when your backend or hosted flow gives your
app a pre-issued token to resume or continue a specific start path.
You can also pass codeChallenge, but in most cases you do not need to. If you omit it,
the SDK automatically generates PKCE and returns a codeVerifier in the completed
update. When your backend already supplies a verifier, the completed update may carry
that value instead.
Handle Flow Updates
flow.onUpdate emits a HawcxV1FlowUpdate. Your app should switch on that state and
render the appropriate UI.
Top-Level Flow States
| Update | What it means |
|---|---|
Idle | No active flow |
Loading | A request is in flight |
Prompt | The backend is asking the user to complete a step |
Completed | Authentication succeeded and returned an authCode |
Error | The flow failed or needs recovery |
User-Visible Prompt Types
| Prompt | What to show | Next SDK call |
|---|---|---|
SelectMethod | Method picker | flow.selectMethod(...) |
EnterCode | OTP input | flow.submitCode(...) |
SetupSms | Phone number input or confirmation | flow.submitPhone(...) |
SetupTotp | TOTP setup UI with secret / QR | flow.submitTotp(...) |
EnterTotp | TOTP code input | flow.submitTotp(...) |
Redirect | Button to continue in browser / hosted flow | flow.oauthCallback(...) after callback |
AwaitApproval | Waiting UI, optionally with QR info | Usually no action; poll if needed |
Some steps are SDK-managed
Some protocol steps, such as device-trust handling, should not be rendered in your UI.
The SDK handles those steps for you when you initialize the V6 flow through
buildProtocolV1Sdk(...).
Recommended Prompt Handling Pattern
import com.hawcx.protocol.v1.HawcxV1FlowUpdate
import com.hawcx.protocol.v1.HawcxV1UserPrompt
fun handle(update: HawcxV1FlowUpdate) {
when (update) {
HawcxV1FlowUpdate.Idle -> {
// Show identifier input
}
is HawcxV1FlowUpdate.Loading -> {
// Show loading state
}
is HawcxV1FlowUpdate.Prompt -> {
val traceId = update.context.meta.traceId
val stepLabel = update.context.stepInfo?.label
val riskMessage = update.context.risk?.message
when (val prompt = update.prompt) {
is HawcxV1UserPrompt.SelectMethod -> {
// Render available methods
println(prompt.methods.map { it.id })
}
is HawcxV1UserPrompt.EnterCode -> {
// Render OTP UI and resend countdown
println(prompt.destination)
println(prompt.resendAt ?: "")
}
is HawcxV1UserPrompt.SetupSms -> {
// Collect or confirm phone number
println(prompt.existingPhone ?: "")
}
is HawcxV1UserPrompt.SetupTotp -> {
// Render authenticator setup UI
println(prompt.secret)
println(prompt.otpauthUrl)
}
HawcxV1UserPrompt.EnterTotp -> {
// Render TOTP code input
}
is HawcxV1UserPrompt.Redirect -> {
// Open hosted step and resume through oauthCallback(...)
println(prompt.url)
println(prompt.returnScheme ?: "")
}
is HawcxV1UserPrompt.AwaitApproval -> {
// Show waiting UI
println(prompt.qrData ?: "")
println(prompt.expiresAt)
println(prompt.pollInterval)
}
}
println(traceId)
println(stepLabel ?: "")
println(riskMessage ?: "")
}
is HawcxV1FlowUpdate.Completed -> {
// Send authCode + codeVerifier to your backend
// Capture session for correlation or support logs if useful
println(update.session)
println(update.authCode)
println(update.expiresAt)
println(update.codeVerifier ?: "")
}
is HawcxV1FlowUpdate.Error -> {
// Surface a user-friendly error and capture traceId for support
println(update.code)
println(update.action)
println(update.message)
println(update.retryable)
println(update.details)
println(update.meta?.traceId ?: "")
}
}
}Use Step Labels and Risk Metadata
Each prompt context may include extra metadata:
context.stepInfo- helps you label the current stage, such as Verify Identity or Second Factor
context.risk- helps you show a step-up or risk warning if the backend flags the session
context.meta.traceId- should be captured in logs or support tickets for debugging
These are optional but strongly recommended for a polished production UI.
Resend Codes
For email OTP and SMS OTP steps, call:
val didResend = hawcx.flow.resend()If didResend is false, the resend window is not open yet. Use the resendAt value
from the current EnterCode prompt to drive your countdown or “try again later” UI.
Handle Redirect Steps
If the backend returns a Redirect prompt, open the hosted flow in a browser or custom
tab, register a callback intent filter, parse the callback, and resume the Hawcx flow.
Add a callback scheme to your app:
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="yourapp" />
</intent-filter>
</activity>Then handle the callback in your activity:
import android.content.Intent
import android.net.Uri
import androidx.fragment.app.FragmentActivity
import com.hawcx.protocol.v1.HawcxV1OAuthCallbackParser
import com.hawcx.protocol.v1.HawcxV1SDK
class MainActivity : FragmentActivity() {
private lateinit var hawcx: HawcxV1SDK
private fun openRedirect(url: String) {
startActivity(
Intent(Intent.ACTION_VIEW, Uri.parse(url))
)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (intent.action != Intent.ACTION_VIEW) return
val callback = HawcxV1OAuthCallbackParser.parse(intent.dataString) ?: return
hawcx.flow.oauthCallback(callback.code, callback.state)
}
}If your app can receive the callback while the flow screen is not active, queue or route
the callback to your active auth coordinator before calling flow.oauthCallback(...).
Handle Completion and Backend Exchange
When the flow completes, the SDK returns the values your app needs to finish sign-in with your backend:
sessionauthCodeexpiresAtcodeVerifierwhen PKCE is part of the completed flowmetafor correlation details such astraceId
Use authCode and codeVerifier as the primary inputs for the backend exchange.
expiresAt and meta.traceId are useful for timing, telemetry, and support logs, but
they are not the core exchange inputs. session is also optional and mainly useful for
correlation or support tooling.
Include codeVerifier whenever the SDK returns it. If your app supplied a custom
codeChallenge, send the matching verifier from that flow to your backend as part of the
exchange.
Send the completion payload to your backend immediately over HTTPS. Do not perform the code exchange from the Android app.
Recommended payload shape:
{
"authCode": "<authCode>",
"codeVerifier": "<optional-codeVerifier>",
"identifier": "[email protected]",
"session": "<optional-session>"
}Add any app-specific context your backend needs, but keep the authorization code exchange and token verification on the server.
You usually do not need to send session to your backend unless you want it for
correlation, analytics, or support logs.
Your backend should:
- exchange
authCodeandcodeVerifierwith the Hawcx backend SDK - verify the returned claims
- create your app session or tokens
- return the app auth result your Android app needs
Recommended pattern
For most production apps, backend code exchange is the right default. Keep OAuth client credentials and token verification logic on your server.
Android app-to-backend example
In this example, identifier is the same email, username, or phone number you used to
start the Hawcx flow.
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
private val backendClient = OkHttpClient()
fun exchangeWithBackend(
authCode: String,
codeVerifier: String?,
identifier: String,
session: String?
) {
val payload = JSONObject().apply {
put("authCode", authCode)
put("identifier", identifier)
put("session", session)
if (!codeVerifier.isNullOrBlank()) {
put("codeVerifier", codeVerifier)
}
}
val request = Request.Builder()
.url("https://your-backend.example.com/api/hawcx/exchange")
.post(payload.toString().toRequestBody("application/json".toMediaType()))
.build()
backendClient.newCall(request).enqueue(object : okhttp3.Callback {
override fun onFailure(call: okhttp3.Call, e: java.io.IOException) {
// Show an error state or retry option in your app.
}
override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) {
response.use {
if (!response.isSuccessful) {
// Handle backend exchange failure.
}
}
}
})
}
when (update) {
is HawcxV1FlowUpdate.Completed -> {
exchangeWithBackend(
authCode = update.authCode,
codeVerifier = update.codeVerifier,
identifier = identifier,
session = update.session
)
}
else -> Unit
}This example shows the app-to-backend handoff only. Your backend then uses the Hawcx backend SDK to exchange and verify the code before it creates the user session.
Backend implementation guides:
Optional SDK-Managed Exchange
If your app truly needs an SDK-managed exchange path, the framework also provides
HawcxV1OAuthExchangeService. Treat that as an advanced option rather than the default
integration path. When configured with a verifier or a non-empty publicKeyPem, it can
verify the returned ID token and returns a HawcxV1OAuthToken (idToken,
tokenType, expiresIn) rather than a V5-style access / refresh token pair.
Trusted Devices and Secure Storage
When you initialize Android through HawcxSDK(...).buildProtocolV1Sdk(...), V6
automatically manages trusted-device material for you:
- Device trust prompts are handled internally by the SDK
- Device credentials are stored per app, per user, and per Hawcx project / Config ID
- Secure storage is isolated so the same user in two different apps does not share credentials
- If local device-trust material becomes invalid, the SDK clears it and re-enrolls on the next flow when needed
Your UI does not need to implement the crypto flow directly.
If you instantiate HawcxV1SDK directly as an advanced integration, you must also
provide the Android device-info and device-crypto wiring yourself for trusted-device
steps to work the same way.
Polling and Waiting Steps
When the backend returns AwaitApproval, the SDK can poll automatically:
autoPollApprovals = truekeeps the flow moving without extra app code- set
autoPollApprovals = falseonly if your app needs manual control over waiting steps - when polling is manual, call
hawcx.flow.poll()using the interval returned by the prompt
Reset, Cancel, and Recovery
Use these methods for flow control:
hawcx.flow.cancel() // cancel the active flow
hawcx.reset() // clear in-memory flow state and return to idleWhat reset() Does
- Clears the current flow session and prompt state
- Stops any in-progress approval polling
- Returns the flow to
Idle
What reset() Does Not Do
- It does not log the user out of your backend
- It does not revoke your app session
- It does not automatically remove trusted-device material
For app logout, clear your own app session or backend-issued tokens. If you also use
existing Hawcx utilities such as push approvals, device sessions, or token-storage helpers,
those remain on HawcxSDK and can continue to coexist with HawcxV1SDK.
Error Handling
When the flow emits Error(...), use:
codefor programmatic handlingmessagefor a user-visible summaryactionwhen present to decide whether to retry, restart, wait, resend, or abortdetails.retryAt/details.retryAfterSecondsfor timingmeta.traceIdfor support and debugging
action can be null for some internal or unsupported states, so always fall back to
code and retryable as your primary recovery signals.
Common actions include:
| Error Action | Recommended UI behavior |
|---|---|
RETRY_INPUT | Let the user correct the current input |
RESTART_FLOW | Return to identifier entry and start again |
WAIT | Show countdown or disabled state |
RETRY_REQUEST | Retry automatically or let the user retry |
RESEND_CODE | Offer resend |
SELECT_METHOD | Return to method selection |
ABORT | End the flow and show failure |
Troubleshooting
401 / 403 / Unauthorized
Check:
configIdis correctbaseUrlpoints to the correct Hawcx tenant host root- the project is configured correctly in the Hawcx dashboard
OTP Resend Is Disabled
Your app is likely still inside the resend cooldown window. Use resendAt from the
current EnterCode prompt and wait until the timer expires.
Device Trust Keeps Re-Enrolling
If the local trusted-device record becomes invalid or the device environment changes, the SDK may clear the local record and re-enroll. This is expected recovery behavior.