> ## Documentation Index
> Fetch the complete documentation index at: https://www.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](/docs/javascript/building-ui/legacy-wallet-upgrade) 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

<Tabs>
  <Tab title="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>

  <Tab title="React Native (WebView)">
    If you mount the app in a `WebView` instead of using the [popup route](/docs/javascript/building-ui/legacy-wallet-upgrade/popup), 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](/docs/javascript/building-ui/legacy-wallet-upgrade/redirect) instead.
  </Tab>
</Tabs>

## See also

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