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

# Deep link best practices

> Open Lightning wallets, proxy API keys, and track Orchestra orders with SSE in browser-based onramp and Pay Link flows.

Onramp and Pay Links both hand off from a web page to a Lightning wallet on the user's phone through a deep link. The three browser-side patterns the handoff needs: opening the wallet reliably, tracking the order without leaking your API key, and showing the user what stage their payment is in.

<CardGroup cols={2}>
  <Card title="Live example" href="https://flashnet-onramp-example.vercel.app">
    flashnet-onramp-example.vercel.app
  </Card>

  <Card title="Source code" href="https://github.com/flashnetxyz/pay-link-example">
    github.com/flashnetxyz/pay-link-example
  </Card>
</CardGroup>

## How do I open the Lightning app?

The Cash App payment URL is a deep link. It only works when the browser navigates to it. A `fetch()` call just downloads the HTML response and nothing visible happens.

**Wrong:**

```ts theme={null}
// This silently fetches HTML, so Cash App never opens
await fetch(cashAppPaymentUrl);
```

**Right:**

```ts theme={null}
// This navigates the browser, which triggers the Cash App universal link
window.location.href = cashAppPaymentUrl;
```

The same rule applies to Strike, Wallet of Satoshi, and any other Lightning app that handles a universal link. Navigate to the URL; do not fetch it.

## How do I track order status with SSE?

Use Server-Sent Events for live order tracking. Connect through your proxy so the API key stays server-side. Close the connection on terminal statuses (`completed`, `failed`, `expired`, `unfulfilled`, `refunded`). Fall back to polling every 3 seconds if SSE disconnects.

```ts theme={null}
const es = new EventSource(`/api/proxy/v1/sse/operations/${orderId}`);

es.addEventListener("status", (e) => {
  const { status } = JSON.parse(e.data);
  updateUI(status);
  if (["completed", "failed", "expired", "unfulfilled", "refunded"].includes(status)) es.close();
});
```

<Warning>
  Proxy SSE through your backend. The browser should never see your API key. Detect `/v1/sse/` paths in your proxy and stream the response with `text/event-stream` headers.
</Warning>

<Accordion title="Full useOrderSSE hook (src/lib/use-order-sse.ts)">
  ```ts theme={null}
  "use client";

  import { useEffect, useRef, useState } from "react";

  const TERMINAL_STATUSES = new Set(["completed", "failed", "expired", "unfulfilled", "refunded"]);

  interface UseOrderSSEParams {
    orderId: string;
    onStatus: (status: string) => void;
    enabled?: boolean;
  }

  export function useOrderSSE({
    orderId,
    onStatus,
    enabled = true,
  }: UseOrderSSEParams): { connected: boolean } {
    const [connected, setConnected] = useState(false);
    const onStatusRef = useRef(onStatus);
    onStatusRef.current = onStatus;

    useEffect(() => {
      if (!enabled || !orderId) return;

      const url = `/api/proxy/v1/sse/operations/${encodeURIComponent(orderId)}`;
      const es = new EventSource(url);

      es.addEventListener("status", (e) => {
        try {
          const data = JSON.parse(e.data) as { status: string };
          onStatusRef.current(data.status);
          if (TERMINAL_STATUSES.has(data.status)) {
            es.close();
            setConnected(false);
          }
        } catch {
          // ignore malformed events
        }
      });

      es.onopen = () => setConnected(true);
      es.onerror = () => setConnected(false);

      return () => {
        es.close();
        setConnected(false);
      };
    }, [orderId, enabled]);

    return { connected };
  }
  ```
</Accordion>

## How do I proxy the API key?

Keep the API key on the server. The browser calls your proxy, the proxy attaches the `Authorization` header, and the response streams back. For SSE paths, the proxy streams the upstream body as-is with `text/event-stream` headers so the browser keeps the connection open.

<Accordion title="SSE proxy handler (src/app/api/proxy/[...path]/route.ts)">
  ```ts theme={null}
  // SSE: stream the response back as-is
  if (isSSE && upstream.body) {
    return new Response(upstream.body, {
      status: upstream.status,
      headers: {
        "Content-Type": "text/event-stream",
        "Cache-Control": "no-cache",
        Connection: "keep-alive",
      },
    });
  }
  ```
</Accordion>

## How do I show pipeline progress?

Orders move through a sequence of statuses. Surface the current stage so the user knows the payment is working.

| Status       | Meaning                                      |
| ------------ | -------------------------------------------- |
| `confirming` | Payment detected, waiting for confirmation   |
| `swapping`   | Converting BTC to stablecoin                 |
| `bridging`   | Sending to destination chain (if applicable) |
| `completed`  | Funds delivered                              |

Use the SSE status events to animate transitions between stages. The [live example](https://flashnet-onramp-example.vercel.app) shows a step-by-step pipeline visualization with a countdown timer.
