Documentation
/
SDK Reference
/
Frontend
/
Android
/
Hawcx Android SDK (V6)

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:

  1. you are starting a new Android integration
  2. you want an adaptive flow with backend-selected primary, MFA, and trusted-device steps
  3. you are implementing the current Hawcx Android release path in your app

Use the V5 Android guide if:

  1. your app already ships the existing HawcxSDK V4/V5 integration
  2. you still depend on the older authenticateV5 / submitOtpV5 or V4 flows
  3. 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:

  1. Create or select your project
  2. Configure the primary authentication step
  3. Configure MFA and trusted-device behavior if needed
  4. Generate a Config ID for the SDK

For product configuration details, see:

  1. Projects
  2. Config IDs
  3. Auth Flow Configuration

How the V6 Flow Works

  1. Your app initializes HawcxSDK with your tenant host and obtains a HawcxV1SDK wrapper for the V6 flow.
  2. You start the flow with a HawcxV1FlowType, usually SIGNIN, plus the user's identifier.
  3. The backend responds with the next step for that user and project configuration.
  4. Your app renders the current prompt:
    • choose a method
    • enter an OTP
    • set up SMS
    • set up TOTP
    • continue a redirect
    • wait for approval
  5. When the flow completes, the SDK returns authCode and, when applicable, codeVerifier.
  6. Your backend exchanges the code and creates the user session.

Integration Responsibilities

Your app is responsible for:

  1. collecting the user's identifier
  2. rendering the current prompt
  3. sending the user's input back to the SDK
  4. handling redirects when prompted
  5. sending the final authorization code to your backend

The SDK handles:

  1. protocol requests and required headers
  2. PKCE generation when needed
  3. trusted-device storage and secure device credentials
  4. device-trust processing
  5. approval polling when the flow requires it

Your backend handles:

  1. exchanging the authorization code
  2. verifying returned claims
  3. creating the app session
  4. 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

  1. Android API 26+
  2. A Kotlin- or Java-based Android app
  3. A Hawcx Config ID for your project
  4. Your Hawcx tenant base URL

Example base URL:

https://stage-api.hawcx.com

For 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

  1. HawcxSDK is 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.
  2. buildProtocolV1Sdk(...) returns the HawcxV1SDK wrapper that powers the V6 prompt-driven flow.
  3. configId is 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.
  4. baseUrl should point to your Hawcx tenant host root. When you initialize Android through HawcxSDK(...), let the SDK derive the legacy and protocol routes from that host.
  5. autoPollApprovals defaults to true, which is the right choice for most apps.
  6. relyingParty is optional. Set it only when your backend expects an X-Relying-Party header for this integration.
  7. If your app still uses the older HawcxSDK surface, 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:

  1. holding the current HawcxV1FlowUpdate
  2. calling start(...)
  3. reacting to flow.onUpdate
  4. 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:

  1. SIGNIN
  2. SIGNUP
  3. ACCOUNT_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

UpdateWhat it means
IdleNo active flow
LoadingA request is in flight
PromptThe backend is asking the user to complete a step
CompletedAuthentication succeeded and returned an authCode
ErrorThe flow failed or needs recovery

User-Visible Prompt Types

PromptWhat to showNext SDK call
SelectMethodMethod pickerflow.selectMethod(...)
EnterCodeOTP inputflow.submitCode(...)
SetupSmsPhone number input or confirmationflow.submitPhone(...)
SetupTotpTOTP setup UI with secret / QRflow.submitTotp(...)
EnterTotpTOTP code inputflow.submitTotp(...)
RedirectButton to continue in browser / hosted flowflow.oauthCallback(...) after callback
AwaitApprovalWaiting UI, optionally with QR infoUsually 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(...).

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:

  1. context.stepInfo
    • helps you label the current stage, such as Verify Identity or Second Factor
  2. context.risk
    • helps you show a step-up or risk warning if the backend flags the session
  3. 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:

  1. session
  2. authCode
  3. expiresAt
  4. codeVerifier when PKCE is part of the completed flow
  5. meta for correlation details such as traceId

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:

  1. exchange authCode and codeVerifier with the Hawcx backend SDK
  2. verify the returned claims
  3. create your app session or tokens
  4. 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:

  1. Device trust prompts are handled internally by the SDK
  2. Device credentials are stored per app, per user, and per Hawcx project / Config ID
  3. Secure storage is isolated so the same user in two different apps does not share credentials
  4. 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:

  1. autoPollApprovals = true keeps the flow moving without extra app code
  2. set autoPollApprovals = false only if your app needs manual control over waiting steps
  3. 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 idle

What reset() Does

  1. Clears the current flow session and prompt state
  2. Stops any in-progress approval polling
  3. Returns the flow to Idle

What reset() Does Not Do

  1. It does not log the user out of your backend
  2. It does not revoke your app session
  3. 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:

  1. code for programmatic handling
  2. message for a user-visible summary
  3. action when present to decide whether to retry, restart, wait, resend, or abort
  4. details.retryAt / details.retryAfterSeconds for timing
  5. meta.traceId for 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 ActionRecommended UI behavior
RETRY_INPUTLet the user correct the current input
RESTART_FLOWReturn to identifier entry and start again
WAITShow countdown or disabled state
RETRY_REQUESTRetry automatically or let the user retry
RESEND_CODEOffer resend
SELECT_METHODReturn to method selection
ABORTEnd the flow and show failure

Troubleshooting

401 / 403 / Unauthorized

Check:

  1. configId is correct
  2. baseUrl points to the correct Hawcx tenant host root
  3. 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.