Skip to navigation

Phone Number Silent Network Authentication

Phone Number Silent Network Authentication (SNA) checks that a phone number belongs to the SIM card in the user’s device:

  • The check runs over the device’s cellular data connection.
  • The user doesn’t type a code, and Persona shows no UI.

With Sentinel, SNA runs on Transactions:

  • Your server starts the check by creating a Transaction with the phone number.
  • Your app runs the carrier step with Persona’s SNA SDK. It needs only the Transaction ID and a client token.
  • Your Workflow gets the result and continues.

This guide covers SNA with the Sentinel SDKs. If your app also uses the Inquiry SDK, see Use the Inquiry SDK with Sentinel and SNA.

On iOS, transaction-initiated SNA isn’t released yet. The iOS run API in this guide is the planned API and may change before release.

How it works

  1. Your server creates a Transaction with the user’s phone number. It sends the Transaction ID and a client token to your app.
  2. Your app calls SilentNetworkAuthentication.run with those two values.
  3. Your Workflow runs on the new Transaction and creates an SNA Verification for its phone number.
  4. The SDK claims the Verification’s carrier check, runs it over cellular data and submits the result.
  5. Persona checks the result, and your Workflow continues down the branch for the Verification’s status.

Packages

Sentinel and SNA are separate packages:

PlatformCapabilityPackageMinimum OS
AndroidSentinelcom.withpersona.sdk2:sentinelAndroid 6.0 (API 23)
AndroidSNAcom.withpersona.sdk2:sna-implAndroid 7.0 (API 24)
iOSSentinelPersonaSentinel productiOS 15
iOSSNAPersonaSna productiOS 15
  • SNA is not part of the Sentinel package on either platform.
  • You can run SNA without Sentinel. Add Sentinel only if you also collect Sentinel signals.
  • The same SNA package also runs SNA steps in Inquiries. See Use the Inquiry SDK with Sentinel and SNA.

For approximate sizes, see Size impact.

Install

Add the packages for each platform your app supports.

Android

sna-impl is Persona’s SNA artifact. It’s optional and separate from sentinel, and it comes from the same repository as the other Persona Android artifacts.

repositories {
// ...
maven {
url = uri("https://sdk.withpersona.com/android/releases")
}
}
android {
// ...
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
dependencies {
// Sentinel
implementation("com.withpersona.sdk2:sentinel:X.Y.Z")
// SNA (not part of Sentinel)
implementation("com.withpersona.sdk2:sna-impl:X.Y.Z")
}

A few details about these dependencies:

  • Use the same version as your other Persona Android artifacts.
  • If you only use SNA, leave out the sentinel line.
  • sna-impl requires Android 7.0 (API 24), because Vonage’s client library does. Set minSdkVersion to 24 or higher in apps that include it. Sentinel alone needs API 23.
  • sna-impl pulls in OkHttp, Moshi, Kotlin coroutines and Vonage’s client library 1.0.0.
  • sna-impl doesn’t depend on the Inquiry SDK or on Sentinel.

For the full Sentinel setup, see the Sentinel Android Integration Guide.

iOS

Add Persona with Swift Package Manager. These packages aren’t published to CocoaPods.

  1. In Xcode, select your project’s Swift Packages tab and click +.
  2. Enter https://github.com/persona-id/inquiry-ios-2 and add the package.
  3. Select the PersonaSentinel product for your app target.
  4. For transaction-initiated SNA, also select PersonaSna once its run API is released.

iOS 15 or later is required.

Sentinel used to ship in the separate sentinel-ios package, which no longer gets releases. Add PersonaSentinel from inquiry-ios-2 instead.

For the full Sentinel setup, see the Sentinel iOS Integration Guide.

Set up once

Set up these pieces once, in the Dashboard or with the API:

  1. A Transaction type with a phone number field, for example phone_number. See Transaction types.

  2. A Workflow that runs SNA on each new Transaction of that type. See Building Workflows.

    • Trigger it when a Transaction of the type is created.
    • Add a Run Verification step that runs your SNA Verification template on the Transaction’s phone number field.
    • Branch on the Verification’s status. For example, tag the Transaction when it passes, and fall back to another check when it fails.
    • In Production, the Run Verification step waits for the Verification to finish before the Workflow continues. For Sandbox, see Test SNA.
  3. Optionally, a webhook subscribed to verification.passed and verification.failed, if you read the outcome from webhooks. See Webhooks.

  4. A client token for your app. Create it on your server with your API key, using Create a Client Token.

    • Never put an API key in the app.
    • List verification.sna.submit in permissions.
    • List the Transaction type’s ID in allowed-resources. The token can’t run SNA for a Transaction type you don’t list.
    curl -X POST https://api.withpersona.com/api/v1/client-tokens \
    -H "Authorization: Bearer $PERSONA_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
    "data": {
    "attributes": {
    "name": "Mobile app SNA",
    "permissions": ["verification.sna.submit"],
    "allowed-resources": ["txntp_..."],
    "version-expires-in-seconds": 86400
    }
    }
    }'
    • Setting permissions replaces the default set (sentinel.collect and txn.write). If your app uses the same token for Sentinel, list those too.
    • The token’s value is in the client token version, under the response’s included.
    • The value is returned only when that version is created, so store it on your server.

Run SNA for each Transaction

For each phone number you want to check:

  1. Your server creates the Transaction with the phone number in fields, using Create a Transaction.

    • Use the phone number on file for the user, or one they enter in your app.
    • The SDK doesn’t collect the phone number.
    curl -X POST https://api.withpersona.com/api/v1/transactions \
    -H "Authorization: Bearer $PERSONA_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
    "data": {
    "attributes": {
    "transaction-type-id": "txntp_...",
    "fields": { "phone_number": "+14155550123" }
    }
    }
    }'
    • The response’s data.id is the Transaction ID (txn_...).
    • The Transactions API rejects Sentinel-enabled Transaction types with HTTP 422. Create those through the Sentinel API with a client token instead.
  2. Your server sends the Transaction ID and the client token value to your app right away. The Transaction ID is the only ID the app needs.

  3. Your app calls SilentNetworkAuthentication.run with both values, as in the Android and iOS examples below.

    • The SDK waits for your Workflow to create the SNA Verification.
    • It then runs the carrier check over cellular data and submits the result.
    • The carrier check is only valid for a short time after the Verification is created, so call run promptly.
  4. Persona checks the result, and the Workflow continues down the branch for the Verification’s status. Read the outcome on your server from either:

    • The verification.passed or verification.failed webhook.
    • The Transaction’s status and tags that your Workflow sets.

What run does

run handles the whole device side of the check:

  • Polls Persona about once a second, for up to 2 minutes, until your Workflow has created the SNA Verification and its check is ready.
  • Claims the check for this device. If the response is lost, run retries the claim and gets the same check back.
  • Runs the carrier check over cellular data, with a timeout of at most 10 seconds, bounded by the check’s remaining validity.
  • Submits the code or an error name to Persona, retrying network errors, server errors and transient conflicts up to 3 attempts.

Each Verification hands its check to one device, once. Call run once per Transaction: a second call, on any device, gets unavailable and reports it, which can fail a check that is still running.

Android

Import SilentNetworkAuthentication from the com.withpersona.sdk2.sna package. Call run from a coroutine, or use the Java overload with a callback:

val result = SilentNetworkAuthentication.run(context, transactionId, clientToken)
when (result) {
is SilentNetworkAuthenticationResult.Submitted -> {
// Persona recorded the device's result. Get the outcome on your backend.
}
is SilentNetworkAuthenticationResult.ClientError -> {
// Log result.name, for example "device_not_cellular". Don't parse result.message.
}
is SilentNetworkAuthenticationResult.Failed -> {
// Persona recorded nothing. Check result.reason.
}
}

The Kotlin run is a suspend function. The Java overload takes a Callback fun interface.

SilentNetworkAuthenticationResult is one of:

  • Submitted
  • ClientError(name, message)
  • Failed(reason), where FailureReason is INVALID_INPUT, UNAUTHORIZED, NOT_FOUND, NOT_ACCEPTED, NETWORK, SERVER or CANCELLED

iOS

Transaction-initiated SNA isn’t released on iOS yet. This is the planned API.

Import PersonaSna. Call run from an async context, or pass a completion handler:

let result = await SilentNetworkAuthentication.run(transactionId: transactionId, clientToken: clientToken)
switch result {
case .submitted:
// Persona recorded the device's result. Get the outcome on your backend.
break
case .clientError(let name, _):
// Log name, for example "device_not_cellular". Don't parse the message.
break
case .failed(let failure):
// Persona recorded nothing. Check failure.
break
}

The results are .submitted, .clientError(name:message:) and .failed(SilentNetworkAuthenticationFailure). The failure is one of .invalidInput, .unauthorized, .notFound, .notAccepted, .network, .server(statusCode:) or .cancelled.

Results

Each call returns one of three results:

ResultMeaning
SubmittedThe device’s result was recorded.
Client error (name, message)SNA couldn’t run, or failed, on the device. Persona recorded the stable error name, for example device_not_cellular, timeout_error or integration_error. Don’t parse the free-form message.
FailedPersona recorded nothing. The reason says why (see below).

Failure reasons (Android FailureReason / iOS SilentNetworkAuthenticationFailure):

ReasonMeaning
INVALID_INPUT / .invalidInputThe Transaction ID or client token is empty or malformed. Nothing was sent.
UNAUTHORIZED / .unauthorizedPersona rejected the client token, or the token isn’t allowed to use this Transaction’s type.
NOT_FOUND / .notFoundPersona has no such Transaction for this client token, or your Workflow didn’t create an SNA Verification for it within 2 minutes.
NOT_ACCEPTED / .notAcceptedThe Verification can no longer take a result (it expired, was canceled or was redacted), so retrying won’t help.
NETWORK / .networkPersona couldn’t be reached.
SERVER / .server(statusCode:)Persona answered with an error or a response the SDK doesn’t expect.
CANCELLED / .cancellediOS: the task running run was cancelled, and run stopped without sending anything further. On Android, cancelling the coroutine throws CancellationException instead, and the callback form can’t be cancelled.

The result never says whether the Verification passed. Get the outcome on your backend, from Verification or Transaction webhooks or from the Workflow. Don’t gate sign-in on the SDK result.

Test SNA

Sandbox and Production test different things:

  • Sandbox: no carrier check runs.
    • Persona tells the SDK that SNA is unavailable.
    • The SDK records that and returns ClientError (Android) or .clientError (iOS) with the name unavailable.
    • Persona then completes the Verification with a simulated pass.
    • Sandbox Workflows don’t wait for the SNA Verification. They continue as soon as it’s created.
    • Use Sandbox to check your wiring (token permissions, passing the Transaction ID and calling run), not to test Workflow branches on the SNA outcome.
  • End to end: use Production.
    • Use a phone whose subscriber identity module (SIM) card has the Transaction’s phone number.
    • Turn on cellular data.

Limitations

Transaction-initiated SNA has these limits:

  • Not available in FedRAMP (Federal Risk and Authorization Management Program) environments yet.
    • run always talks to Persona’s default host, withpersona.com.
    • Don’t pass run a client token from a FedRAMP organization.
  • Requires Android 7.0 or later, or iOS 15 or later.
  • SNA works only on devices with an active SIM card or eSIM (embedded SIM) profile.

Collect Sentinel signals (optional)

SNA doesn’t need this step. To also collect Sentinel’s passive device signals, build an event with your client token and Transaction type ID, then trigger it:

Sentinel
.newEventBuilder(
clientToken = CLIENT_TOKEN,
transactionTypeId = TRANSACTION_TYPE_ID,
)
.build()
.trigger(context)

The builders also accept optional context, identifiers, environment ID and Account settings. See the Android and iOS guides for every option.

Use the Inquiry SDK with Sentinel and SNA

Some apps use all three SDKs:

  • The Inquiry SDK runs identity verification flows (Inquiries).
  • Sentinel collects passive device signals on Transactions.
  • SNA checks phone numbers, inside an Inquiry or on a Transaction.

Where SNA runs

SNA runs inStarted byWhat your app does
An InquiryAn SNA step in your Inquiry templateNothing extra. The Inquiry SDK runs the step during the flow.
A TransactionYour server creating a TransactionCalls SilentNetworkAuthentication.run with the Transaction ID and a client token.

Which to use:

  • Use an SNA step in an Inquiry when the user is already going through one, for example during onboarding.
  • Use transaction-initiated SNA to check a phone number you already have without showing a flow, for example when a user signs in on a new device.

Android

Add all three artifacts:

dependencies {
// Inquiry flows
implementation("com.withpersona.sdk2:inquiry:X.Y.Z")
// Sentinel
implementation("com.withpersona.sdk2:sentinel:X.Y.Z")
// SNA, for Inquiry steps and for Transactions
implementation("com.withpersona.sdk2:sna-impl:X.Y.Z")
}
  • Use the same version X.Y.Z for all three.
  • Add sna-impl once. The Inquiry SDK uses it for SNA steps, and your code calls its run for Transactions. Don’t add another SNA artifact.
  • Set minSdkVersion to 24 or higher, because sna-impl needs Android 7.0.
  • For the rest of the Inquiry setup, see the Android Integration Guide.

iOS

  • For Sentinel, select PersonaSentinel from inquiry-ios-2, as in Install.
  • For SNA steps in Inquiries, follow the SNA section of the iOS Integration Guide. It adds PersonaSna and passes PersonaSnaAdapter() to the Inquiry builder’s .snaAdapter.
  • Transaction-initiated SNA with run isn’t released on iOS yet.

Client tokens

  • Inquiries don’t use your SNA client token.
  • Sentinel and transaction-initiated SNA can share one client token. List sentinel.collect, txn.write and verification.sna.submit in its permissions.

Size impact

Sizes are approximate and change from release to release.

PlatformWhatApproximate size
Androidsna-impl added to an app that already uses Sentinelabout 49 KB (they share OkHttp, Moshi and coroutines)
AndroidSentinel alone, over an empty appabout 0.52 MB
iOSPersonaSentinel frameworkabout 280 KB
  • The Android figures are from a release build with R8, arm64-v8a APK, of a minimal app.
  • The iOS figure is the framework’s size in Persona’s framework size report. It isn’t the increase in your app’s download size, so measure in your app.

Next steps

For more on Sentinel, see:

For the Inquiry SDK, see the Android and iOS Integration Guides.