Skip to main content
Possession requires both a client-side SDK and these platform APIs:

Prerequisites

  • Access token — Obtain a bearer token using Prove OAuth (Authentication). Use the same token for POST /v3/unify, POST /v3/unify-status, and POST /v3/unify-bind.
  • Server — Use a Prove server SDK or call those endpoints directly. Use the reference pages for full request bodies, optional fields, and responses.
  • Client SDK — Install the Web SDK for possession checks and the Prove Key.

How to implement

You must integrate the client-side SDK for possession checks and the Prove Key.
1

Determine Type of Flow

Decide if the customer is on a mobile or desktop browser using this example. If the isMobile is true, set mobile as the flowType for the Start() function on the server, otherwise you can set desktop:
// Check if the customer is on a mobile or desktop browser.
const authCheck = new proveAuth.AuthenticatorBuilder().build();
let isMobile = authCheck.isMobileWeb()
2

Initialize the Flow

Mobile Auth in the United States doesn’t require a phone number, but all other countries do.
Send a request to your back end server with the phone number and possession type to start the flow. See POST /v3/unify for all request fields, optional parameters (for example finalTargetURL, deviceId, allowOTPRetry), and the full response.
For OTP retries (allowOTPRetry), implement the client SDK behavior in the Authenticate step.
// Send the Unify request.
rspUnify, err := client.V3.V3UnifyRequest(ctx, &components.V3UnifyRequest{
  PhoneNumber:    "2001004014",
  PossessionType: "mobile",
  ClientRequestID: provesdkservergo.String("client-abc-123"),
  AllowOTPRetry: true,
})
if err != nil {
  t.Fatal(err)
}
Return the authToken to the client for Authenticate(). Persist correlationId for UnifyStatus(). See POST /v3/unify for all response fields, JWT behavior, and session timing.
3

Authenticate

Once you have the authToken, build the authenticator for both the mobile and desktop flows.
For desktop mode, Prove ignores the Prove Key and runs Instant Link.
async function authenticate(isMobileWeb, authToken) {
  // Set up the authenticator for either mobile or desktop flow.
  let builder = new proveAuth.AuthenticatorBuilder();

  if (isMobileWeb) {
    // Set up Mobile Auth and OTP.
    builder = builder
      .withAuthFinishStep((input) => verify(input.authId))
      .withMobileAuthImplementation("fetch")
      .withOtpFallback(otpStart, otpFinish);
  } else {
    // Set up Instant Link.
    builder = builder
      .withAuthFinishStep((input) => verify(input.authId))
      .withInstantLinkFallback(instantLink)
      .withRole("secondary");
  }

  const authenticator = builder.build();

  // Authenticate with the authToken.
  return authenticator.authenticate(authToken);
}

Configure OTP

Use this section to configure SMS OTP fallback in the Web SDK: wire OtpStartStep and OtpFinishStep so the SDK can send the code, collect the PIN, and optionally support resend, OTP retry, or phone-number change.

Prerequisites

  • After Prove sends the SMS, the customer has about two minutes to enter the OTP before the session times out.
  • When building the authenticator, call withOtpFallback(startStep: OtpStartStep | OtpStartStepFn, finishStep: OtpFinishStep | OtpFinishStepFn) and implement both OtpStartStep and OtpFinishStep. Return the phone number from your start step as an object with a phoneNumber field passed to resolve(...); use reject('message') when collection fails. The Prove client SDK orchestrates when each step runs—do not duplicate that orchestration in app code (see Invalid OTP and step orchestration below).
Invalid OTP and step orchestrationWhen the customer enters an invalid OTP, the Prove client SDK detects it and may surface activity related to AuthFinishStep that looks unexpected. This behavior is expected.Do not add your own extra invocation of the OTP finish step to pass the error in otpError. The SDK runs your OtpFinishStep when retry or error UI is needed.Implement the required step functions, but let the Prove client SDK orchestrate when each step runs.

Configure the client

1

Implement start and finish for your use case

Open the tab that matches how the phone number is collected and which OTP options you need.
Use this path when the server already has the phone number (for example from POST /v3/start or your server Start wrapper) and the browser must not prompt for it again.Call resolve(null) (or the equivalent in your snippet) so the SDK knows the customer agreed to receive the SMS. Follow the sample for the exact resolve shape your SDK version expects.
function otpStartStep(phoneNumberNeeded, phoneValidationError) {
  return new Promise((resolve, reject) => {
    // Since no phone number is needed, don't prompt the user.
    resolve(null);
  });
}
Call reject('some error message') if something prevents sending the SMS or completing the flow (for example the customer cancels or leaves the UI with the back button).In the finish step, return the OTP through resolve(result: OtpFinishResult) with the OnSuccess result type and the value wrapped in OtpFinishInput, as in the snippet below.
function otpFinishStep(otpError) {
  return new Promise((resolve, reject) => {
    // If error message is found, handle it.
    if (otpError) {
      // Set to a variable and display it in a field.
      // In this example, we don't do anything with the error.
      var someErrorMessage = otpError.message;
    }

    // Prompt the user for whether they received the SMS.
    // Typically, this is a page that shows the OTP already. We are simplifying
    // it by requiring an input.
    var input = confirm('Did you receive a text message?');
    if (!input) {
      // Close the modal if a text message was not received.
      return;
    }

    // Prompt the user for the OTP.
    var otp = prompt('Enter OTP code:');
    if (otp) {
      // If the input is valid and the user clicked `OK`, return the OTP.
      resolve({
        input: { otp }, // OTP value
        resultType: 0, // OnSuccess enum type = 0
      });
    } else {
      // Else, exit the flow.
      reject('phone invalid or user cancelled');
    }
  });
}
2

Verify the integration

In Sandbox, walk each shipped path (default, prompt, resend, retry if enabled, phone change if enabled). Confirm SMS delivery, OTP entry within the timeout, and that you never stack extra OtpFinishStep invocations on top of the SDK’s invalid-OTP handling—the SDK should remain the single orchestrator for retries and errors.
Use this section to configure the Web SDK so Instant Link SMS can be sent from the browser flow and optional resend or phone-number change behaves as expected.
Instant Link for mobile web isn’t supported.
Custom or vanity Instant Links aren’t supported. You can’t substitute a custom link for the default Instant Link.

Prerequisites

  • Integrate on desktop (or other supported) web; see the callout above for mobile web.
When building the authenticator, use withInstantLinkFallback(startStep: InstantLinkStartStep | InstantLinkStartStepFn, retryStep?: InstantLinkRetryStep | InstantLinkRetryStepFn). Implement InstantLinkStartStep in every flow. Add InstantLinkRetryStep only if you support Resend or Phone Number Change (see those tabs). Return the phone number from your step as an object with a phoneNumber field passed to resolve(...); use reject('message') when collection fails.

Configure the client

1

Implement the Instant Link start and optional retry

Open the tab that matches how the phone number is collected and sent to Prove.
Use this path when the server already has the phone number (for example from POST /v3/start or your server Start wrapper) and the browser must not prompt again.Call resolve(null) (or the equivalent in your snippet) so the SDK knows the customer agreed to receive the SMS. Follow the sample for the exact resolve shape your SDK version expects.
function instantLinkStartStep(phoneNumberNeeded, phoneValidationError) {
  return new Promise((resolve, reject) => {
    // Since no phone number is needed, don't prompt the user.
    resolve(null);
  });
}
2

Verify the integration

In Sandbox, run through each path you ship (default, prompt, and any retry flows). Confirm the SMS sends, the customer can complete the link within the timeout window, and resolve / reject match the UX you expect when the customer cancels or retries.
In the desktop flow, a WebSocket opens for 3 minutes on the desktop browser while waiting for the customer to select the link in the text message. Once clicked, the WebSocket closes and the AuthFinishStep function finishes.
If you’re using Content Security Policy headers, ensure you allow connect-src for wss://*.prove-auth.proveapis.com.
4

Verify Mobile Number

In the AuthFinishStep, specify a function to call once the possession checks complete on the mobile phone. This endpoint on your back end server calls the UnifyStatus() function to validate the phone number. The AuthFinishStep then completes.
rspUnifyStatus, err := client.V3.V3UnifyStatusRequest(context.TODO(), &components.V3UnifyStatusRequest{
CorrelationID: rspUnify.V3UnifyResponse.CorrelationID,
})
if err != nil {
return fmt.Errorf("error on UnifyStatus(): %w", err)
}
See POST /v3/unify-status for the full response schema. Interpret evaluation using the Global Fraud Policy.
If you’re using Content Security Policy headers, ensure you allow connect-src for wss://*.prove-auth.proveapis.com, wss://device.uat.proveapis.com, and wss://device.proveapis.com as required for your environment.
Prove Key validity: The key does not expire on a fixed schedule, but Prove deactivates it on our servers after 12 months of inactivity (each successful authentication resets that period). On iOS, the Prove Key remains on the device after uninstall and reinstall. Device Context is available for the Web SDK only—not the native Android SDK. Pass rebind on /v3/unify to force a full possession check when needed. See Prove Key validity and expiration.
Prove Key persistence enhancementUsing Prove Key in web applications can be affected by strict browser privacy policies aimed at preventing cross-site tracking. For example, Safari completely disables third-party cookies and limits the lifetime of all script-writable website data. Prove Auth uses script-writable website data (localStorage and IndexedDB) for storing Prove Auth deviceId, crypto key material, and other critical metadata for subsequent device re-authentication.If the browser deletes this script-writable data, Prove Auth is no longer able to recognize the device and perform authentication. To mitigate this limitation and avoid repeating the device registration process, follow these steps to enable Device Context.
The device context feature is not enabled by default. Contact Prove to enable this add-on feature.
Web app setupFollow these steps to add Prove Key persistence in your web app:
1

Install the Device Context Module

Install the Device Context integration module and add activation code to your app:
NPM
npm install @prove-identity/prove-auth-device-context
Then activate it in your app:
import * as dcMod from "@prove-identity/prove-auth-device-context";
dcMod.activate();
If you’re using Content Security Policy headers, ensure you allow access to the following common resources:
  • script-src https://fpnpmcdn.net https://*.prove-auth.proveapis.com,
  • connect-src https://api.fpjs.io https://*.api.fpjs.io https://*.prove-auth.proveapis.com
  • worker-src blob:
Additionally, if you’re including the file from jsDelivr, add https://cdn.jsdelivr.net/npm/@prove-identity/ to script-src, plus corresponding sha256-... or nonce-....
2

Configure the Authenticator Builder

When configuring authenticatorBuilder for any flow, add the withDeviceContext() method to enable device context data collection:
// Prove supports integration within the US and EU regions for device context data collection
// The public API Key for each build config will be provided by Prove
const publicApiKey = "apiKeyAssignedForThisBuildConfig";
const buildConfig = BuildConfig.US_UAT;
const options = {
  publicApiKey: publicApiKey,
  buildConfig: buildConfig,
};

// Enable device context data with builder method
let builder = new proveAuth.AuthenticatorBuilder();
builder = builder
  .withAuthFinishStep((input) => verify(input.authId))
  .withMobileAuthImplementation("fetch")
  .withOtpFallback(otpStart, otpFinish)
  .withDeviceContext(options);

const authenticator = builder.build();
Call authenticatorBuilder.build() immediately after the web app loads. This enables the Web SDK to load components for device context collection and collect signals before authentication begins, which helps decrease latency.
3

Enable Cookies for Auth Requests

Ensure cookies are enabled when you make AuthStart and AuthFinish calls:
// Enable cookies for AuthStart
axios.post(backendUrl + '/authStart', data, { withCredentials: true });

// Enable cookies for AuthFinish
axios.post(backendUrl + '/authFinish', data, { withCredentials: true });
Handling multiple device registrationsIf a user registers their device multiple times before using Device Context, Prove Auth may have multiple registration candidates matching the same visitor ID. To improve recovery accuracy:
  1. Store the deviceId returned from successful authentications in your backend (database or web cookie)
  2. Pass the stored deviceId with the /unify request to specify which registration to restore
// Send the Unify request with deviceId for registration recovery
rspUnify, err := client.V3.V3UnifyRequest(ctx, &components.V3UnifyRequest{
  PhoneNumber:    "2001004014",
  PossessionType: "mobile",
  DeviceId:       provesdkservergo.String("stored-device-id-from-previous-auth"),
})
Continue the flow using the response from POST /v3/unify. Return updated session or auth details to the client as needed. Interpret evaluation using the Global Fraud Policy when present.
If you send a different phone number to /unify than the one registered to the Prove key, success=false returns when calling /unify-status. This is because the Prove Key is bound to a different number. Run the flow again with rebind=true in the /unify endpoint. This forces a full possession check and then, if valid, rebinds the Prove Key to the new phone number.

Error handling

If authentication was not successful, the evaluation field returned by UnifyStatus() has failure reasons explaining what went wrong according to the Global Fraud Policy.

Possession failure reasons

CodeDescription
9100Phone possession incomplete.
9101Phone number doesn’t match Prove Key. Rebind required.

Error code 9100 - Phone possession incomplete

For Prove Possession - Desktop and Prove Possession - Mobile, error code 9100 indicates that authentication was not successful.

Error code 9101 - Phone number doesn’t match Prove Key

For Prove Possession - Mobile, error code 9101 indicates that someone attempted authentication using a phone number that doesn’t match the phone number registered to the Prove Key. How to resolve: To rebind the Prove Key to the new phone number, restart the flow with rebind=true in the call to /unify:
rspUnify, err := client.V3.V3UnifyRequest(ctx, &components.V3UnifyRequest{
  PhoneNumber:    "2001004014",
  PossessionType: "mobile",
  Rebind:         provesdkservergo.Bool(true),
})
When you set rebind=true:
  1. Prove initiates a new authentication check.
  2. If the authentication is successful, the Prove Key is rebound to the new phone number passed into the request.
  3. Future authentications with this phone number succeed without requiring rebind.
/v3/unify accepts a correctly formatted phone number as valid regardless of landline or mobile. If SMS or Mobile Auth cannot finish the possession step, expect the issue to surface on /v3/unify-status as success=false, depending on the flow.