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

# Open BotShield from your app

> The hand-off that always works: send the person to BotShield with the request's link, on this phone or by QR code from another screen, and learn the result on your server.

When the [inline passkey](https://docs.botshield.ai/native/inline-passkey.md) can't run, the person confirms in BotShield instead. This is the hand-off every BotShield request supports, and the way through on every inline failure.

Use it when:

* your app isn't [registered](https://docs.botshield.ai/native/register-your-app.md), or runs on Android today;
* the request is for an [Age Gate](https://docs.botshield.ai/gate/age-gate.md);
* `passkey-options` answered `fallback` or failed;
* the device has no BotShield passkey, or the person cancelled and chose **Use the BotShield app instead**;
* the person is on a computer and has BotShield on their phone.

## The link

Use the request's `https` link:

* `web_url` from [`create-verification-link`](https://docs.botshield.ai/gate/verify-on-your-server#pattern-b-server-to-server), or
* `url` from [`passkey-options`](https://docs.botshield.ai/native/inline-passkey). It points at the same request.

Both open `https://app.botshield.ai/verify?request_id=…`.

| The person has… | What happens |
| - | - |
| The BotShield app | The link opens the BotShield app on the request. |
| No BotShield app | The link opens BotShield on the web (public beta). A first-time person creates a passkey there, with no download, and confirms. |

Prefer the `https` link to `deep_link` (`botshield://…`). The `https` link works whether or not the app is installed. `deep_link` fails silently without the app.

> [!WARNING]
>
> On iPhone, iOS opens another app only from a real tap. Open the link straight from the person's tap on **Open BotShield**. If you fetch the request first and open the link after the `await`, iOS may refuse. Fetch the request before you show the button.

## On this phone

**1. Show an Open BotShield button**

Label it **Open BotShield** or **Use the BotShield app instead**.

**2. Open the link outside your app**

Open the URL with the system, not in an in-app web view, so iOS and Android can hand it to the BotShield app.

```swift Swift theme={null}
UIApplication.shared.open(URL(string: url)!)
```

```kotlin Kotlin theme={null}
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
```

```typescript Capacitor theme={null}
window.open(url, "_system");
```

**3. The person confirms in BotShield**

BotShield names your platform and asks the person to confirm with their passkey.

**4. The person comes back**

BotShield doesn't send the person back to your app automatically. They return with the app switcher, or the back link iOS shows in the corner. When your app is active again, check the result.

## From another screen (QR code)

On a tablet, a computer or a TV, draw `web_url` as a QR code with your own QR library. The person scans it with their phone camera and confirms in BotShield. Keep the QR code up until the request completes or `expires_at` passes.

When the person has [secured their account](https://docs.botshield.ai/secured-accounts/overview.md) with you and you sent `partner_user_ref`, BotShield also pushes the request to their phone. `pushed_to_devices` above `0` means it did: tell them to check their phone, and keep the QR code as a fallback.

## Learn the result

The hand-off gives your app nothing back. Your server learns the result:

* **Webhook (preferred):** `gate.human_verified` or `gate.unavailable`, matched by `request_id`.
* **Polling:** `GET /verification/status?request_id=…` every 3 to 5 seconds until it's final or `expires_at` passes.

When the app becomes active again, ask your server whether the request completed. Then [check the token](https://docs.botshield.ai/gate/verify-on-your-server.md) on your server before you let the action through. If `expires_at` passes with no result, treat the request as Unavailable. No webhook is sent for a request nobody opened.

## What to show

| Situation | What to show |
| - | - |
| Waiting | "Finish in BotShield." Keep **Open BotShield** as the retry. |
| The person came back and the request is still pending | "You came back without confirming." Keep **Open BotShield**. |
| `gate.unavailable` with `user_denied` | The person cancelled in BotShield. Nothing happened. Offer to try again. |
| Expired | Offer to start again. Create a new request on your server. |
| Completed | Carry on, once your server has checked the token. |

See [Result states](https://docs.botshield.ai/concepts/result-states.md) for every reason.


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