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

# Handoff

> Signing a user straight into their journey when your own app has already signed them in.

A user who reaches the journey from inside your own app has already signed in to you, and asking for
their mobile number again asks them to prove the same thing twice. A handoff is a single-use address
that opens the journey with them already signed in.

[Hosted](/verification/hosted#two-ways-in) compares this with `journey_url`, the
link on every record that you send over any channel.

```mermaid theme={"system"}
sequenceDiagram
    autonumber
    participant App as Your app
    participant Backend as Your backend
    participant API as Privue
    participant Browser as In-app browser

    App->>Backend: user taps "Verify"
    Backend->>API: POST /verifications
    API-->>Backend: id and journey_url
    Backend->>API: POST /verifications/{id}/handoff
    API-->>Backend: url and expires_in_seconds
    Backend-->>App: url
    App->>Browser: open url
    Browser->>API: journey opens, user signed in
```

Create the verification the way you always do. Creating is idempotent per user and workflow, so you
can create once and hand off on every visit after that.

```bash Create the handoff theme={"system"}
curl -X POST \
  https://api.verify.privue.ai/verifications/3f2504e0-4f89-41d3-9a0c-0305e82c3301/handoff \
  -H "Authorization: Bearer $PRIVUE_API_KEY"
```

```json Response theme={"system"}
{
  "url": "https://verify.privue.ai/acme/merchant-onboarding#ticket=2nR9v6QpKZ8mLxT4bYw1dJhSaEfG7uNc",
  "expires_in_seconds": 120
}
```

## Rules

* **Create the handoff at the moment you open the browser**, not in advance. It is valid for
  `expires_in_seconds`, and it stops working once it has been used.
* **One handoff carries the whole visit.** While that browser keeps its site storage the user stays
  signed in, so backgrounding your app and coming back needs nothing new. Create another when you open a
  new browser, or when you open the journey for a different user.
* **Keep your API key on your server.** Your app asks your backend for the handoff, and your backend asks
  us. A key shipped inside an app is a key you cannot rotate away from your users.
* **Pass the URL through unchanged, fragment included.** Everything after the `#` is what signs the
  user in, so a component that drops or rewrites the fragment breaks the handoff.
* **Treat the URL as a credential.** Do not log it, store it, print it into a QR code, or send it
  anywhere you would not send a password. When you want something you can send, send `journey_url`.

<Warning>
  A handoff that has expired or has already been used is not an error. The journey opens and asks the
  user for their mobile number, exactly as `journey_url` does. A late handoff costs the user a
  sign-in, never the journey.
</Warning>

## What the in-app browser must support

A workflow with a selfie or premises-photos step captures through the camera, and a workflow with a
DigiLocker step sends the user out to a consent screen and back. Whichever browser component you
open the handoff in has to carry both.

| Requirement | Why |
| - | - |
| Camera access for the page | The selfie and premises-photos steps capture live, not from the gallery |
| Site storage and cookies kept for the whole visit | The sign-in the handoff performs is held there |
| One browser instance for the whole journey | The user comes back from DigiLocker consent into the browser they left, still signed in |
| Outbound navigation and return | Consent happens on the provider's own domain, so a component that refuses to leave your host cannot complete that step |

**iOS.** Open the handoff in a `WKWebView` with `allowsInlineMediaPlayback` set, an empty
`mediaTypesRequiringUserActionForPlayback`, `NSCameraUsageDescription` declared in your `Info.plist`,
and a persistent `WKWebsiteDataStore`. `SFSafariViewController` does not offer the camera to the page,
so the selfie and premises-photos steps cannot be completed in it.

**Android.** Open the handoff in a Chrome Custom Tab, or in a `WebView` that holds the `CAMERA` runtime
permission, grants `RESOURCE_VIDEO_CAPTURE` in `WebChromeClient.onPermissionRequest`, and has DOM
storage enabled.

## Getting the user back into your app

On submit the journey redirects to your `return_url`. It must be one of the return URLs registered for
your account and it must use `https`, so a custom scheme such as `acme://kyc/done` is rejected. Register
a universal link (iOS) or an app link (Android) that your app intercepts: submitting then closes the
browser and lands the user back in your own screens.

<Info>
  The redirect carries `verification_id` and `status`, which render a landing screen and are not proof
  of the outcome. Confirm the result by polling `GET /verifications/{verification_id}`, as in
  [Reading the result](/verification/results).
</Info>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.