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

# Going lower-level (build the URL yourself)

> Build the upgrade app URL and own the mounting and completion handling yourself with buildUpgradeUrl and subscribeToUpgradeCompletion — the escape hatch behind the redirect, embed, and popup routes.

The redirect, embed, and popup routes cover almost every case. Reach for this route only when you need to **own the mounting and lifecycle yourself** — a custom window manager, a bespoke iframe host, or a React Native `WebView`. See [the overview](https://docs.dynamic.xyz/docs/javascript/building-ui/legacy-wallet-upgrade.md) for detection, hosting, and the shared CORS requirements.

There are two building blocks. The three ready-made routes are built on exactly these:

1. **`buildUpgradeUrl({ redirectUrl }, client?)`** — returns the upgrade app URL. It reads `environmentId` from your client and validates the return URL for you, so all you own is where you point it.
2. **React to completion** — this is the branch point, and it depends on *how* you mounted the app.

## 1. Build the URL

```typescript theme={"system"}
import { buildUpgradeUrl } from '@dynamic-labs-sdk/legacy-embedded-wallet-upgrade';

// environmentId comes from your client; redirectUrl defaults to the current page.
const upgradeUrl = buildUpgradeUrl();

// Mount it however you like — your own <iframe>, window.open, or a WebView.
```

## 2. React to completion

> [tab] Web (iframe / window)
>
> When you host the app in an iframe or a window you opened, it signals completion with an **origin-checked `postMessage`**. `subscribeToUpgradeCompletion` is the DOM-agnostic primitive that listens for it, refreshes the session on success, and hands you the outcome. Always call the returned unsubscribe function when your surface unmounts.
>
> ```typescript theme={"system"}
> import {
>   buildUpgradeUrl,
>   subscribeToUpgradeCompletion,
> } from '@dynamic-labs-sdk/legacy-embedded-wallet-upgrade';
>
> function mountUpgrade(container: HTMLElement) {
>   const iframe = document.createElement('iframe');
>   iframe.src = buildUpgradeUrl();
>   container.appendChild(iframe);
>
>   const unsubscribe = subscribeToUpgradeCompletion({
>     // Session already refreshed on success. onDone is your implementation.
>     onComplete: ({ status }) => {
>       unsubscribe();
>       iframe.remove();
>       onDone(status);
>     },
>   });
>
>   // Also call unsubscribe() if the user dismisses your surface first.
>   return unsubscribe;
> }
> ```

> [tab] React Native (WebView)
>
> If you mount the app in a `WebView` instead of using the [popup route](https://docs.dynamic.xyz/docs/javascript/building-ui/legacy-wallet-upgrade/popup.md), point the WebView's source at `buildUpgradeUrl(...)` and read the outcome yourself.
>
> `subscribeToUpgradeCompletion` **does not apply here** — inside a WebView the app's `postMessage` surfaces through the WebView's own `onMessage` prop (via `window.ReactNativeWebView`), not a parent-window `message` event. Read the completion payload off that prop, or use the deep-link [redirect route](https://docs.dynamic.xyz/docs/javascript/building-ui/legacy-wallet-upgrade/redirect.md) instead.

## See also

* [Upgrade overview](https://docs.dynamic.xyz/docs/javascript/building-ui/legacy-wallet-upgrade.md) — detection, hosting, and shared requirements
* [Redirect to the app](https://docs.dynamic.xyz/docs/javascript/building-ui/legacy-wallet-upgrade/redirect.md)
* [Embed the app (iframe)](https://docs.dynamic.xyz/docs/javascript/building-ui/legacy-wallet-upgrade/embed.md)
* [Popup (new window)](https://docs.dynamic.xyz/docs/javascript/building-ui/legacy-wallet-upgrade/popup.md)


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