> ## Documentation Index
> Fetch the complete documentation index at: https://docs.authsignal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Push verification

> Implement app verification with push notifications using the Authsignal SDKs for iOS, Android, React Native, and Flutter.

<Info>
  Check out our end-to-end guide on [how to implement app verification with push
  notifications](/authentication-methods/app-verification/push).
</Info>

## Adding a credential

Adding a push credential generates a private/public key pair, where the private key is secured on the user's mobile device and the public key is held by Authsignal.

This operation must be authorized with a short-lived token, which can be obtained by [tracking an action from your backend](/advanced-usage/authenticator-binding#tracking-an-action-to-generate-a-token) in an authenticated context.

<CodeGroup>
  ```swift iOS theme={null}
  await authsignal.push.addCredential(token: "eyJhbGciOiJ...")
  ```

  ```kotlin Android theme={null}
  authsignal.push.addCredential(token = "eyJhbGciOiJ...")
  ```

  ```ts React Native theme={null}
  await authsignal.push.addCredential({ token: "eyJhbGciOiJ..." });
  ```

  ```dart Flutter theme={null}
  await authsignal.push.addCredential("eyJhbGciOiJ...");
  ```
</CodeGroup>

### Parameters

<ResponseField name="token" type="string">
  A short-lived token obtained by [tracking an
  action](/advanced-usage/authenticator-binding#tracking-an-action-to-generate-a-token).
</ResponseField>

### Response

<ResponseField name="response" type="AuthsignalResponse<AppCredential>">
  <Expandable title="properties">
    <ResponseField name="error" type="string">
      An unstructured error description present if the SDK call encountered an error.
    </ResponseField>

    <ResponseField name="errorCode" type="string">
      A formatted error code which may additionally be present if the SDK call encountered an error.
      Possible values are: `token_not_set`, `expired_token` or `network_error`.
    </ResponseField>

    <ResponseField name="data" type="AppCredential | null">
      <Expandable title="properties">
        <ResponseField name="credentialId" type="string" required>
          The ID of the app credential.
        </ResponseField>

        <ResponseField name="createdAt" type="string" required>
          The date and time the app credential was created.
        </ResponseField>

        <ResponseField name="userId" type="string" required>
          The user ID of the user that the app credential belongs to.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

## Getting a credential

Get information about the push credential stored on the device, if one exists.

<CodeGroup>
  ```swift iOS theme={null}
  let response = await authsignal.push.getCredential()
  ```

  ```kotlin Android theme={null}
  val response = authsignal.push.getCredential()
  ```

  ```ts React Native theme={null}
  const response = await authsignal.push.getCredential();
  ```

  ```dart Flutter theme={null}
  final response = await authsignal.push.getCredential();
  ```
</CodeGroup>

### Response

<ResponseField name="response" type="AuthsignalResponse<AppCredential>">
  <Expandable title="properties">
    <ResponseField name="error" type="string">
      An unstructured error description present if the SDK call encountered an error.
    </ResponseField>

    <ResponseField name="errorCode" type="string">
      A formatted error code which may additionally be present if the SDK call encountered an error.
      Possible values are: `invalid_credential` when the credential exists on the device but has
      been removed from the server.
    </ResponseField>

    <ResponseField name="data" type="AppCredential">
      <Expandable title="properties">
        <ResponseField name="credentialId" type="string" required>
          The ID of the app credential.
        </ResponseField>

        <ResponseField name="createdAt" type="string" required>
          The date and time the app credential was created.
        </ResponseField>

        <ResponseField name="lastAuthenticatedAt" type="string">
          The date and time the app credential was last used for authentication. Will be null if the
          credential has never been used.
        </ResponseField>

        <ResponseField name="userId" type="string" required>
          The user ID of the user that the app credential belongs to.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

## Updating a credential

Push tokens are not permanent. The OS rotates them - on app reinstall or restore to a new device, after clearing app data, when a token expires, or whenever the push service decides to issue a new one. The token you registered with [`addCredential`](#adding-a-credential) eventually goes stale, and a notification sent to a stale token is silently dropped.

Use `updateCredential` to point the existing credential at a fresh device token. Unlike `addCredential`, it requires **no token from your backend and no user session** - the device proves possession of its existing credential, so you can call it from a background handler without any user interaction. It also leaves the credential's keys in place, so the user stays enrolled.

<CodeGroup>
  ```swift iOS theme={null}
  await authsignal.push.updateCredential(pushToken: "<APNs or FCM token>")
  ```

  ```kotlin Android theme={null}
  authsignal.push.updateCredential(pushToken = "<FCM token>")
  ```

  ```ts React Native theme={null}
  await authsignal.push.updateCredential({ pushToken: "<APNs or FCM token>" });
  ```
</CodeGroup>

### When to call it

Both Apple and Google recommend the same pattern: subscribe to the OS token callback and [send the new token to your server as soon as it changes](https://firebase.google.com/docs/cloud-messaging/manage-tokens#detect-invalid-token-responses-from-the-fcm-backend). With Authsignal, "send to your server" means calling `updateCredential`. Register the latest token whenever it changes so Authsignal always has a deliverable one:

* **On token rotation** - the most important trigger. Subscribe to the OS push token callback and update the credential whenever a new token is issued.
  * **iOS, Default provider** - the raw APNs token, delivered to [`application(_:didRegisterForRemoteNotificationsWithDeviceToken:)`](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/application\(_:didregisterforremotenotificationswithdevicetoken:\)).
  * **Android, and iOS with the Firebase provider** - the FCM registration token, delivered to [`onNewToken`](https://firebase.google.com/docs/cloud-messaging/android/client#monitor-token-generation) (Android) or [`messaging(_:didReceiveRegistrationToken:)`](https://firebase.google.com/docs/cloud-messaging/ios/client#token-swizzle-disabled) (iOS).
  * **React Native** - [`messaging().onTokenRefresh`](https://rnfirebase.io/messaging/usage#getting-the-device-token) covers the FCM token (Android, and iOS in Firebase mode). For the raw APNs token in Default mode, use Expo Notifications' [`addPushTokenListener`](https://docs.expo.dev/versions/latest/sdk/notifications/#addpushtokenlistenerlistener), which surfaces the same `didRegisterForRemoteNotificationsWithDeviceToken` callback to JavaScript. Pass the token the listener receives straight to `updateCredential` rather than re-fetching it, as Expo warns that calling `getDevicePushTokenAsync` inside the listener can re-trigger it.
* **On app foreground or launch** - retrieve the current token, [compare it against the one you last registered](https://firebase.google.com/docs/cloud-messaging/manage-tokens), and update the credential if it changed. This is a backstop for tokens that rotated while the app was killed, when no rotation callback fires.

Skip the call if no push credential exists yet - the first token is registered by [`addCredential`](#adding-a-credential) at enrolment. The token type must match your tenant's [configured provider](/authentication-methods/app-verification/managed-push#which-token-to-register), the same as for `addCredential`.

### Keeping the credential alive

`updateCredential` has a second use. If you configure a [credential lifetime](/authentication-methods/app-verification/enrollment-lifecycle#keeping-credentials-alive) on the push authenticator, credentials expire unless kept alive. Pass `resetExpiry` to reset the credential's lease and keep the device enrolled. Both parameters are optional, so you can rotate the token, reset the expiry, or do both in one call.

<CodeGroup>
  ```swift iOS theme={null}
  // Reset the lease without changing the token
  await authsignal.push.updateCredential(resetExpiry: true)

  // Rotate the token and reset the lease together
  await authsignal.push.updateCredential(pushToken: "<APNs or FCM token>", resetExpiry: true)
  ```

  ```kotlin Android theme={null}
  // Reset the lease without changing the token
  authsignal.push.updateCredential(resetExpiry = true)

  // Rotate the token and reset the lease together
  authsignal.push.updateCredential(pushToken = "<FCM token>", resetExpiry = true)
  ```

  ```ts React Native theme={null}
  // Reset the lease without changing the token
  await authsignal.push.updateCredential({ resetExpiry: true });

  // Rotate the token and reset the lease together
  await authsignal.push.updateCredential({ pushToken: "<APNs or FCM token>", resetExpiry: true });
  ```
</CodeGroup>

When no lifetime is configured, credentials never expire and you don't need `resetExpiry`. See [Keeping credentials alive](/authentication-methods/app-verification/enrollment-lifecycle#keeping-credentials-alive) for where to call this in your app's lifecycle.

Each successful `updateCredential` call also fires the [`authenticator.updated`](/advanced-usage/webhooks/authenticator-updated) webhook, so your backend can track token rotations and expiry extensions without polling.

<Note>
  `updateCredential` is supported on the iOS SDK from `v2.11.0`, the Android SDK from `v4.1.0`, and the React Native SDK from `v3.1.0`.
</Note>

### Response

<ResponseField name="response" type="AuthsignalResponse<UpdatedAppCredential>">
  <Expandable title="properties">
    <ResponseField name="error" type="string">
      An unstructured error description present if the SDK call encountered an error. This could
      occur if no credential exists on the device, or if the credential is no longer valid because
      the corresponding user authenticator has been deleted in the Authsignal Portal.
    </ResponseField>

    <ResponseField name="data" type="UpdatedAppCredential">
      <Expandable title="properties">
        <ResponseField name="userAuthenticatorId" type="string" required>
          The ID of the user authenticator backing the credential.
        </ResponseField>

        <ResponseField name="userId" type="string" required>
          The user ID of the user that the app credential belongs to.
        </ResponseField>

        <ResponseField name="lastVerifiedAt" type="string" required>
          The date and time the credential was last verified, updated as part of this call.
        </ResponseField>

        <ResponseField name="pushToken" type="string">
          The push token now registered against the credential.
        </ResponseField>

        <ResponseField name="expiresAt" type="string">
          The date and time the credential expires, in ISO 8601 format. Only present when a
          [credential lifetime](/authentication-methods/app-verification/enrollment-lifecycle#keeping-credentials-alive)
          is configured.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

## Removing a credential

<CodeGroup>
  ```swift iOS theme={null}
  await authsignal.push.removeCredential()
  ```

  ```kotlin Android theme={null}
  authsignal.push.removeCredential()
  ```

  ```ts React Native theme={null}
  await authsignal.push.removeCredential();
  ```

  ```dart Flutter theme={null}
  await authsignal.push.removeCredential();
  ```
</CodeGroup>

### Response

<ResponseField name="response" type="AuthsignalResponse<boolean>">
  <Expandable title="properties">
    <ResponseField name="error" type="string">
      An unstructured error description present if the SDK call encountered an error. This could
      occur if the credential on the device is no longer valid because the corresponding user
      authenticator has been deleted in the Authsignal Portal.
    </ResponseField>

    <ResponseField name="data" type="boolean">
      True if the app credential was successfully removed.
    </ResponseField>
  </Expandable>
</ResponseField>

## Getting a challenge

<CodeGroup>
  ```swift iOS theme={null}
  let response = await authsignal.push.getChallenge()

  if let error = result.error {
      // The credential stored on the device is invalid
  } else if let challenge = result.data {
      // A pending challenge request is available
      // Present the user with a prompt to approve or deny the request
      let challengeId = challenge.challengeId
  } else {
      // No pending challenge request
  }
  ```

  ```kotlin Android theme={null}
  val response = authsignal.push.getChallenge()

  if (response.error != null) {
      // The credential stored on the device is invalid
  } else if (response.data != null) {
      // A pending challenge request is available
      // Present the user with a prompt to approve or deny the request
      val challengeId = response.data.challengeId
  } else {
      // No pending challenge request
  }
  ```

  ```ts React Native theme={null}
  const {data, error} = await authsignal.push.getChallenge();

  if (error) {
      // The credential stored on the device is invalid
  } else if (data) {
      // A pending challenge request is available
      // Present the user with a prompt to approve or deny the request
      val challengeId = data.challengeId
  } else {
      // No pending challenge request
  }
  ```

  ```dart Flutter theme={null}
  final response = await authsignal.push.getChallenge();

  if (response.error != null) {
      // The credential stored on the device is invalid
  } else if (response.data != null) {
      // A pending challenge request is available
      // Present the user with a prompt to approve or deny the request
      final challengeId = response.data.challengeId;
  } else {
      // No pending challenge request
  }
  ```
</CodeGroup>

### Response

<ResponseField name="response" type="AuthsignalResponse<DeviceChallenge>">
  <Expandable title="properties">
    <ResponseField name="error" type="string">
      An unstructured error description present if the SDK call encountered an error. This could
      occur if the credential on the device is no longer valid because the corresponding user
      authenticator has been deleted in the Authsignal Portal.
    </ResponseField>

    <ResponseField name="data" type="DeviceChallenge">
      <Expandable title="properties">
        <ResponseField name="challengeId" type="string" required>
          The ID of the challenge corresponding to a particular device credential authentication
          request.
        </ResponseField>

        <ResponseField name="actionCode" type="string">
          The action code identifying the action associated with the challenge.
        </ResponseField>

        <ResponseField name="idempotencyKey" type="string">
          The idempotency key identifying the action associated with the challenge.
        </ResponseField>

        <ResponseField name="userAgent" type="string">
          The user agent of the device which initiated the challenge. Present if passed as input to
          the original track request.
        </ResponseField>

        <ResponseField name="deviceId" type="string">
          The device ID of the device which initiated the challenge. Present if passed as input to
          the original track request.
        </ResponseField>

        <ResponseField name="ipAddress" type="string">
          The IP address of the device which initiated the challenge. Present if passed as input to
          the original track request.
        </ResponseField>

        <ResponseField name="custom" type="object">
          Action-scope custom data associated with the challenge. Only [custom data points marked as
          public](/actions-rules/rules/custom-data-points#public-custom-data-points) are returned.
        </ResponseField>

        <ResponseField name="user" type="object">
          <Expandable title="properties">
            <ResponseField name="custom" type="object">
              User-scope custom data for the user being challenged. Only [custom data points marked
              as public](/actions-rules/rules/custom-data-points#public-custom-data-points) are
              returned.
            </ResponseField>
          </Expandable>
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

## Updating a challenge

After presenting the user with a prompt to approve or reject the request, you should update the challenge with their response.

<CodeGroup>
  ```swift iOS theme={null}
  await authsignal.push.updateChallenge(
      challengeId: challengeId,
      approved: true
  )
  ```

  ```kotlin Android theme={null}
  authsignal.push.updateChallenge(
      challengeId = challengeId,
      approved = true
  )
  ```

  ```ts React Native theme={null}
  await authsignal.push.updateChallenge({
    challengeId,
    approved: true,
  });
  ```

  ```dart Flutter theme={null}
  await authsignal.push.updateChallenge(
      challengeId,
      true,
  );
  ```
</CodeGroup>

### Response

<ResponseField name="response" type="AuthsignalResponse<boolean>">
  <Expandable title="properties">
    <ResponseField name="error" type="string">
      An unstructured error description present if the SDK call encountered an error. This could
      occur if the credential on the device is no longer valid because the corresponding user
      authenticator has been deleted in the Authsignal Portal.
    </ResponseField>

    <ResponseField name="data" type="boolean">
      True if the device challenge was successfully updated.
    </ResponseField>
  </Expandable>
</ResponseField>

## Requiring user authentication

When adding a credential for push verification, it is possible to require that the user authenticate via their OS biometrics or PIN whenever they access the credential (e.g. when approving or rejecting a challenge).

### iOS

To require user authentication on iOS, set the `userPresenceRequired` flag to true when adding the credential.

```swift theme={null}
await authsignal.push.addCredential(
    token: token,
    userPresenceRequired: true
)
```

This is all that's needed - iOS will automatically handle displaying the biometrics or PIN prompt when [updating a challenge](#updating-a-challenge) or [verifying a device](#verifying-a-device).

### Android

To require user authentication on Android, set the `userAuthenticationRequired` flag to true when adding the credential.

```kotlin theme={null}
authsignal.push.addCredential(
    token = token,
    userAuthenticationRequired = true
)
```

It's also possible to specify additional [user authentication parameters](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setUserAuthenticationParameters\(int,%20int\)).

```kotlin theme={null}
authsignal.push.addCredential(
    token = token,
    userAuthenticationRequired = true,
    timeout = 60,
    authorizationType = 0
)
```

The `timeout` param determines the time (in seconds) that the credential can be accessed after authenticating - or `0` if authentication must occur for every credential use.
The `authorizationType` param determines if authentication is required via biometrics and/or device credential (e.g. pin).

If user authentication is required for a credential, you **must** call `updateChallenge` in a [biometric prompt authentication callback](https://developer.android.com/identity/sign-in/biometric-auth).

1. Initialize a signature before displaying the biometric prompt

```kotlin theme={null}
val signature = authsignal.push.startSigning()

val cryptoObject = CryptoObject(signature)

// Initialize biometric prompt and prompt info
val biometricPrompt = ...
val promptInfo = ...

biometricPrompt.authenticate(promptInfo, cryptoObject);
```

2. Retrieve the signature from the crypto object in your prompt's authentication callback

```kotlin theme={null}
override fun onAuthenticationSucceeded(result: AuthenticationResult) {
    super.onAuthenticationSucceeded(result)

    val cryptoObject = result.cryptoObject
    val signer = cryptoObject.signature

    authsignal.push.updateChallenge(
        challengeId = challengeId,
        approved = true,
        signer = signer
    )
}
```
