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 iOS 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

When using the iOS SDK, set mobile as the flowType for the Start() function on the server.
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.
Implement OTP or Instant Link - not both.
Swift
// Object implementing ProveAuthFinishStep protocols
let finishStep = FinishAuthStep()

// Objects implementing OtpStartStep/OtpFinishStep protocols
let otpStartStep = MobileOtpStartStep()
let otpFinishStep = MobileOtpFinishStep()

let proveAuthSdk: ProveAuth
proveAuthSdk = ProveAuth.builder(authFinish: finishStep)
  .withOtpFallback(otpStart: otpStartStep, otpFinish: otpFinishStep)
  .build()
If a mobile data connection is unavailable during testing, use the Builder class. It permits simulated successful session results while connected to a Wi-Fi network. Testing using a Wi-Fi connection is useful in the Sandbox environment.
Swift
proveAuthSdk = ProveAuth.builder(authFinish: finishStep)
  .withMobileAuthTestMode() // Test mode flag
  .build()
The Prove Auth object is thread safe and used as a singleton. Most Prove Auth methods are blocking and therefore can’t execute in the main app thread. The app employs an executor service with a minimum of two threads to manage threads due to the SDK’s ability to process concurrent blocking requests.
Swift
// authToken retrieved from your server via StartAuthRequest
proveAuthSdk.authenticate(authToken) { error in
  DispatchQueue.main.async {
    self.messages.finalResultMessage = "ProveAuth.authenticate returned error: \(error.localizedDescription)"
    print(self.messages.finalResultMessage)
  }
}

Configure OTP

Use this section to configure SMS OTP fallback on iOS: 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(otpStart: otpStartStep, otpFinish: otpFinishStep) and implement both OtpStartStep and OtpFinishStep. The Prove client SDK orchestrates when each step runs—implement the protocols, but 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 (for example, a reported error such as code 10001 with a message that the PIN does not match). You may also see flow activity related to AuthFinishStep (the handler you pass to ProveAuth.builder(authFinish:)) that looks like an unexpected redirect.This behavior is expected. Do not add your own follow-up call to OtpFinishStep to pass the validation error in otpError. The SDK invokes OtpFinishStep.execute(otpError:callback:) when a retry or error UI is needed.Your app should implement the required step protocols, but the Prove client SDK orchestrates when each step runs. Do not duplicate that orchestration in application code.

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 client must not prompt for it again.Call callback.onSuccess(input: nil) so the SDK knows the customer agreed to receive the SMS.
Swift
class OtpStartStepNoPrompt: OtpStartStep {
    @ObservedObject var sheetObservable: SheetObservable
    var callback: OtpStartStepCallback?

    init(sheetObservable: SheetObservable) {
        self.sheetObservable = sheetObservable
    }

    // Implement this method to handle phone number collection for SMS OTP,
    // or to obtain user confirmation for initiating an SMS message.
    func execute(
        phoneNumberNeeded: Bool, phoneValidationError: ProveAuthError?, callback: OtpStartStepCallback
    ) {
        self.callback = callback
        // Since no phone number is needed, don't prompt the user.
        callback.onSuccess(input: nil)
    }
}
Call callback.onError() if something prevents sending the SMS (for example the customer cancels or leaves the flow with the back button).In the finish step, return the OTP the customer entered through your OtpFinishStep implementation, as in the snippet below.
Swift
class OtpFinishStepNoPrompt: OtpFinishStep {
    @ObservedObject var sheetObservable: SheetObservable
    var callback: OtpFinishStepCallback?

    init(sheetObservable: SheetObservable) {
        self.sheetObservable = sheetObservable
    }

    // Implement this method to collect the OTP value delivered via SMS.
    func execute(otpError: ProveAuthError?, callback: OtpFinishStepCallback) {
        self.callback = callback
        // Handle the OTP validation error if present.
        // Signal to UI components to display OtpFinishView
        DispatchQueue.main.async {
            if case .otpValidationError = otpError {
                print("found otpError: \(String(describing: otpError?.localizedDescription))")
                // Signal to your UI components that the last provided OTP is invalid
                self.sheetObservable.isOtpValidationError = true
            } else {
                self.sheetObservable.isOtpValidationError = false
            }
            self.sheetObservable.isOtpFinishActive = true
        }
    }

    // Provide the collected OTP value to the SDK for validation.
    func handleOtp(_ otp: String) {
        guard let callback = self.callback else {
            print("Error: OtpFinishStepCallback is not set ")
            return
        }

        let otpFinishInput = OtpFinishInput(otp: otp)
        callback.onSuccess(input: otpFinishInput)
    }

    // Notify the SDK of any issues encountered while obtaining the OTP value or if the user cancels the OTP flow.
    func handleOtpFinishError() {
        guard let callback = self.callback else {
            print("Error: OtpFinishStepCallback is not set ")
            return
        }

        callback.onError()
    }
}
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 manually chain OtpFinishStep on top of the SDK’s invalid-OTP handling—the SDK should remain the single orchestrator for retries and errors.
Instant Link for iOS is an add-on feature. To enable, contact your Prove representative.
Use this section to configure the iOS client so an Instant Link SMS opens your app and you pass the returned redirect URL to the SDK to resume the session.

Prerequisites

  • Instant Link is enabled for your project (contact your Prove representative if needed).
  • Universal Links (recommended) so the SMS redirect opens your app with the full URL string. See Supporting universal links in your app in the Apple documentation.
When building the authenticator, use withInstantLinkFallback(startStep: InstantLinkStartStep, retryStep: InstantLinkRetryStep?). Implement InstantLinkStartStep in every flow. Add InstantLinkRetryStep only if you support Resend or Phone Number Change (see those tabs). When the client supplies a number, pass it in InstantLinkStartInput (for example the phoneNumber field) to callback.onSuccess(...).

Configure the client

1

Implement the Instant Link start step

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 your initial Start call) and the client must not prompt again.Call callback.onSuccess(input: nil) so the SDK knows the customer agreed to receive the SMS.
Swift
class InstantLinkStartStepNoPrompt: InstantLinkStartStep {
    @ObservedObject var sheetObservable: SheetObservable
    var callback: InstantLinkStartStepCallback?

    init(sheetObservable: SheetObservable) {
        self.sheetObservable = sheetObservable
    }

    // Implement this method to handle phone number collection for instant link,
    // or to obtain user confirmation for initiating an instant link.
    func execute(
        phoneNumberNeeded: Bool,
        phoneValidationError: ProveAuthError?,
        callback: InstantLinkStartStepCallback
    ) {
        self.callback = callback
        // Since no phone number is needed, don't prompt the user.
        callback.onSuccess(input: nil)
    }
}
2

Handle the redirect and resume the session

After you implement the start step above and the user finishes the web step outside your app, Prove redirects to the finalTargetUrl from your server Start call. Your Universal Link handling must deliver that URL into your app so you can pass the full string—including query parameters—into finishInstantLink(redirectUrl:).For how finalTargetUrl fits into server-side Start with Mobile Auth and Instant Link fallback, see Prove Pre-Fill implementation guide.Call finishInstantLink from the entry point that receives the opened URL, for example application(_:open:options:) or application(_:continue:restorationHandler:).
Swift
/// Finishes the Instant Link authentication flow using the redirect URL from the deep link.
/// This is the URL to which the user is redirected after tapping the Instant Link in SMS.
finishInstantLink(redirectUrl: redirectUrl) { error in
  // Handle errors due to invalid format of redirectUrl here
}
The redirect URL is your original finalTargetUrl plus parameters the SDK needs. Example: if Start used https://yourDeepLinkUrl.com, the link might look like https://yourDeepLinkUrl.com?asc=true&authId=some-uuid-string.
ParameterMeaning
asc"true" or "false": whether the server considers the auth session complete.
authIdUUID for the session; the SDK uses it to match the redirect to the in-progress client session.
If required parameters are missing or invalid, the SDK surfaces an error via the finishInstantLink completion and does not continue the flow.Verify: In Sandbox, complete a flow where the SMS opens your app with the full URL; finishInstantLink should run and the session should resume without an error from a well-formed redirect.
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.
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

Error code 9100 indicates that authentication was not successful.

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

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.