# Bring a web app

Make your warehouse board or household planner available inside Notify. Register its origin and send updates that open the relevant page. Add optional microAuth() when your site needs sign-in.

HTML: https://notify.belweave.ai/docs/web-apps

## Add your site

Bring an HTTPS site you control into Notify using `notify apps create`. A repeated registration of the same URL updates its name, icon, or project. Ordinary websites open without sign-in. An integrated site can explicitly request microAuth(); the viewer then approves sign-in and chooses any profile details to share. New sharing choices start off. Manage the profile in Settings → microAuth profile and per-app grants in Access & details.

```bash
notify apps create --name "Warehouse board"   --url https://warehouse.example.com --icon https://warehouse.example.com/icon.png

notify apps list
notify apps remove app_example_warehouse
```

Use an HTTPS launch URL. Only `localhost` development URLs may use plain HTTP. Each account can register up to 100 apps. Agent management requires `apps:read`/`apps:write`; reauthorize if these scopes are missing.

The iOS hosted view requires iOS 18.4+. It supports standard web inputs for camera, microphone, location, motion, and files. iOS builds with device features additionally expose Promise-based `window.notify.haptics(style)`, `authenticate(reason)`, `authenticateChallenge(challenge)`, `speech.start()`/`speech.stop()`, `share(data)`, `openSettings()`, and `capabilities()`. Pocket-friendly optional helpers on both platforms include `haptic(style)` with light, success, or warning, no-argument `authenticate(): Promise<boolean>` for local biometrics without passcode fallback, and `dictate(): Promise<string>` for native speech. Passing a reason to `authenticate(reason)` retains the detailed result object. Keep keyboard input available and handle permission denial or recognition errors. Local Face ID does not require passkey domain association. Native Android builds with device features expose `getToken()`, `close()`, `capabilities()`, `haptics()` (also `haptic()`), `authenticate()`, `speech.start()`/`speech.stop()`, `share()`, and `openSettings()`. Android authentication and speech use an app-name and exact-origin Allow once dialog for each request. Android does not provide signed `authenticateChallenge` proofs, file/download, location, or inline-media integrations. Android passkeys are limited to `https://pocket.belweave.ai`, API 28+, and supported WebKit credential integration; they require Pocket Digital Asset Links association with the actual signed app certificate. A true capability flag does not verify that association; iOS passkeys require AASA and Associated Domains. Neither association follows automatically from registering a hosted app. Feature-detect for platform/build/browser differences. Review the app-level grant and OS permission separately. On iOS before 27, blocking the Notify location wrapper cannot fully gate raw WebKit geolocation; set Location to Never in system settings for a complete OS-level revocation. Local biometric authentication is an owner check, not server identity proof; a signed challenge requires your service to bind the key and validate a fresh nonce.

> On iOS, Settings → App device permissions stores Ask/Allow/Block per account, app, and origin. Allow does not bypass OS denial, and Ask again does not reset OS/WebKit grants. On iOS versions before 27, raw WebKit location calls can bypass the wrapper’s revocation; disable Notify’s global Location permission to fully stop that access. A local biometric check is not server identity. Server authorization needs a verified pass, verified WebAuthn assertion, or an enrolled and verified challenge proof.

## Establish the viewer’s identity

For new integrations, download the SDK from [Notify Tools](https://github.com/Belweave/notify-tools) and copy the release’s complete `sdk` directory into your project as `vendor/notify`. Import browser helpers from `./vendor/notify/browser.mjs`. Feature-detect `supportsMicroAuth()` and call `connectMicroAuth({ challengePath, sessionPath, fields })` from your site’s sign-in control. Your server uses `createMicroAuthServer` from `./vendor/notify/server.mjs` with a durable challenge store. The SDK verifies the signed pass against a fresh, single-use challenge bound to an HttpOnly browser cookie, then your server creates its own session. Current microAuth support targets iOS bridge version 3; keep ordinary browser and older-client sign-in available.

Optional profile fields are name, email, phone, timeZone, pronouns, and custom text. The viewer sees actual values and chooses each custom detail separately. The app receives only requested and approved values. Profile values are self-described; email is the Notify account email, not an independent verification claim. Revoking access stops future passes but does not end the website’s existing sessions or erase copies it already received.

1. For legacy integrations, feature-detect the hosted bridge, then call `await window.notify.getToken()` to obtain the short-lived pass. `window.notify.close()` exits the hosted view.
2. Send the pass to your backend. Verify the ES256 JWT against [Notify’s JWKS](https://notify.belweave.ai/.well-known/jwks.json), using a JWT verifier such as `jose.jwtVerify`.
3. Require issuer `https://notify.belweave.ai`, your exact app origin as audience, protected header `typ: "notify-pass+jwt"`, and algorithm `ES256`. Check expiry and reject replayed `jti` values before creating your own session.

| Field | Type | Description |
| --- | --- | --- |
| `sub` | string | Pairwise user ID (`hk_…`) scoped to your origin. Different origins receive different subjects; apps registered on the same origin share this subject. |
| `aud` | string | Your app's origin, e.g. `https://app.example.com`. |
| `iat / exp` | number | Passes expire two minutes after issue. |
| `jti` | string | Unique per pass; reject reused values to block replay. |
| `app_id` | string | The Notify app ID the pass was issued for. |
| `name / email` | string | Present only when the owner shares them. New sharing choices start off. microAuth() also limits the pass to the fields the site requested. |
| `team_id / team_role` | string | Present for [team apps](https://notify.belweave.ai/docs/teams#teams-apps): the viewer's team and their current role (`owner`, `admin`, or `member`). |

> Exchange the verified pass for your server’s session; do not store the two-minute pass as a permanent login credential. The native Android app’s browser-PKCE device sign-in is a separate protocol from this hosted-site pass.

## Open the right page

When an update belongs to a hosted app, attach its `appId` (CLI: `--app`). Tapping opens that app. An optional `url` points to a page within the registered origin; cross-origin destinations are rejected.

```json
{
  "body": "The pickup schedule is ready",
  "appId": "app_example_warehouse",
  "url": "https://warehouse.example.com/reports/latest"
}
```
