> This page is for version 2020-05-18.
> For other versions, use one of these documentation indexes:
> - 2026-09-29 (default): https://docs.withpersona.com/2026-09-29/llms.txt
> - 2025-12-08: https://docs.withpersona.com/2025-12-08/llms.txt
> - 2025-10-27: https://docs.withpersona.com/2025-10-27/llms.txt
> - 2023-01-05: https://docs.withpersona.com/2023-01-05/llms.txt
> - 2022-09-01: https://docs.withpersona.com/2022-09-01/llms.txt
> - 2021-07-05: https://docs.withpersona.com/2021-07-05/llms.txt
> - 2021-05-14: https://docs.withpersona.com/2021-05-14/llms.txt
> - 2020-05-18: https://docs.withpersona.com/2020-05-18/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.withpersona.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.withpersona.com/_mcp/server.

# 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](#use-the-inquiry-sdk-with-sentinel-and-sna).

> **Note**
>
> 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:

| Platform | Capability | Package                         | Minimum OS           |
| -------- | ---------- | ------------------------------- | -------------------- |
| Android  | Sentinel   | `com.withpersona.sdk2:sentinel` | Android 6.0 (API 23) |
| Android  | SNA        | `com.withpersona.sdk2:sna-impl` | Android 7.0 (API 24) |
| iOS      | Sentinel   | `PersonaSentinel` product       | iOS 15               |
| iOS      | SNA        | `PersonaSna` product            | iOS 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](#use-the-inquiry-sdk-with-sentinel-and-sna).

For approximate sizes, see [Size impact](#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.

**`Kotlin DSL`**

```kotlin Kotlin DSL
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")
}
```

**`Groovy`**

```groovy Groovy
repositories {
  // ...
  maven {
    url '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](/2020-05-18/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.

> **Info**
>
> 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](/2020-05-18/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](/2020-05-18/transaction-types).
2. **A Workflow that runs SNA on each new Transaction of that type.** See [Building Workflows](/2020-05-18/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](#test-sna).
3. **Optionally, a webhook subscribed to `verification.passed` and `verification.failed`**, if you read the outcome from webhooks. See [Webhooks](/2020-05-18/webhooks).
4. **A client token for your app.** Create it on your server with your API key, using [Create a Client Token](/2020-05-18/api-reference/client-tokens/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.

   ```bash
   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](/2020-05-18/api-reference/transactions/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.

   ```bash
   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:

**`Kotlin`**

```kotlin Kotlin
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.
    }
}
```

**`Java`**

```java Java
SilentNetworkAuthentication.run(context, transactionId, clientToken, result -> {
    // Handle the SilentNetworkAuthenticationResult
});
```

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

> **Note**
>
> 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:

**`Swift`**

```swift Swift
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
}
```

**`Swift (completion)`**

```swift Swift (completion)
SilentNetworkAuthentication.run(transactionId: transactionId, clientToken: clientToken) { result in
    // Called once, on the main queue.
}
```

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:

| Result                           | Meaning                                                                                                                                                                                               |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Submitted                        | The 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`. |
| Failed                           | Persona recorded nothing. The reason says why (see below).                                                                                                                                            |

Failure reasons (Android `FailureReason` / iOS `SilentNetworkAuthenticationFailure`):

| Reason                            | Meaning                                                                                                                                                                                                               |
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `INVALID_INPUT` / `.invalidInput` | The Transaction ID or client token is empty or malformed. Nothing was sent.                                                                                                                                           |
| `UNAUTHORIZED` / `.unauthorized`  | Persona rejected the client token, or the token isn't allowed to use this Transaction's type.                                                                                                                         |
| `NOT_FOUND` / `.notFound`         | Persona has no such Transaction for this client token, or your Workflow didn't create an SNA Verification for it within 2 minutes.                                                                                    |
| `NOT_ACCEPTED` / `.notAccepted`   | The Verification can no longer take a result (it expired, was canceled or was redacted), so retrying won't help.                                                                                                      |
| `NETWORK` / `.network`            | Persona couldn't be reached.                                                                                                                                                                                          |
| `SERVER` / `.server(statusCode:)` | Persona answered with an error or a response the SDK doesn't expect.                                                                                                                                                  |
| `CANCELLED` / `.cancelled`        | iOS: 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. |

> **Warning**
>
> 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:

**`Kotlin`**

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

**`Swift`**

```swift Swift
Sentinel
    .builder(
        clientToken: CLIENT_TOKEN,
        transactionTypeId: TRANSACTION_TYPE_ID
    )
    .build()
    .trigger { result in
      // log or handle the result
    }
```

The builders also accept optional context, identifiers, environment ID and Account settings. See the [Android](/2020-05-18/sentinel-android-integration-guide) and [iOS](/2020-05-18/sentinel-ios-integration-guide) 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 in   | Started by                           | What your app does                                                                  |
| ------------- | ------------------------------------ | ----------------------------------------------------------------------------------- |
| An Inquiry    | An SNA step in your Inquiry template | Nothing extra. The Inquiry SDK runs the step during the flow.                       |
| A Transaction | Your server creating a Transaction   | Calls `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:

**`Kotlin DSL`**

```kotlin Kotlin DSL
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")
}
```

**`Groovy`**

```groovy Groovy
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](/2020-05-18/android-sdk-v2-integration-guide).

### iOS

* For Sentinel, select `PersonaSentinel` from `inquiry-ios-2`, as in [Install](#install).
* For SNA steps in Inquiries, follow the SNA section of the [iOS Integration Guide](/2020-05-18/ios-sdk-v2-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.

| Platform | What                                                  | Approximate size                                      |
| -------- | ----------------------------------------------------- | ----------------------------------------------------- |
| Android  | `sna-impl` added to an app that already uses Sentinel | about 49 KB (they share OkHttp, Moshi and coroutines) |
| Android  | Sentinel alone, over an empty app                     | about 0.52 MB                                         |
| iOS      | `PersonaSentinel` framework                           | about 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:

* [Sentinel overview](/2020-05-18/sentinel)
* [Sentinel Android Integration Guide](/2020-05-18/sentinel-android-integration-guide)
* [Sentinel iOS Integration Guide](/2020-05-18/sentinel-ios-integration-guide)
* [Sentinel Web Integration Guide](/2020-05-18/sentinel-web-integration-guide)

For the Inquiry SDK, see the [Android](/2020-05-18/android-sdk-v2-integration-guide) and [iOS](/2020-05-18/ios-sdk-v2-integration-guide) Integration Guides.