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

<AgentInstructions>

## Submitting Feedback

If you encounter incorrect, outdated, or confusing documentation on this page, submit feedback:

POST https://docs.ton.org/feedback

```json
{
  "path": "/ecosystem/ton-pay/quick-start",
  "feedback": "Description of the issue"
}
```

Only submit feedback when you have something specific and actionable to report.

</AgentInstructions>

# Quick start

export const Aside = ({type = "note", title = "", icon = "", iconType = "regular", children}) => {
  const asideVariants = ["note", "tip", "caution", "danger"];
  const asideComponents = {
    note: {
      outerStyle: "border-sky-500/20 bg-sky-50/50 dark:border-sky-500/30 dark:bg-sky-500/10",
      innerStyle: "text-sky-900 dark:text-sky-200",
      calloutType: "note",
      icon: <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg" className="w-4 h-4 text-sky-500" aria-label="Note">
          <path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.3C10.14 1.3 12.7 3.86 12.7 7C12.7 10.14 10.14 12.7 7 12.7C5.48908 12.6974 4.0408 12.096 2.97241 11.0276C1.90403 9.9592 1.30264 8.51092 1.3 7C1.3 3.86 3.86 1.3 7 1.3ZM7 0C3.14 0 0 3.14 0 7C0 10.86 3.14 14 7 14C10.86 14 14 10.86 14 7C14 3.14 10.86 0 7 0ZM8 3H6V8H8V3ZM8 9H6V11H8V9Z"></path>
        </svg>
    },
    tip: {
      outerStyle: "border-emerald-500/20 bg-emerald-50/50 dark:border-emerald-500/30 dark:bg-emerald-500/10",
      innerStyle: "text-emerald-900 dark:text-emerald-200",
      calloutType: "tip",
      icon: <svg width="11" height="14" viewBox="0 0 11 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg" className="text-emerald-600 dark:text-emerald-400/80 w-3.5 h-auto" aria-label="Tip">
          <path d="M3.12794 12.4232C3.12794 12.5954 3.1776 12.7634 3.27244 12.907L3.74114 13.6095C3.88471 13.8248 4.21067 14 4.46964 14H6.15606C6.41415 14 6.74017 13.825 6.88373 13.6095L7.3508 12.9073C7.43114 12.7859 7.49705 12.569 7.49705 12.4232L7.50055 11.3513H3.12521L3.12794 12.4232ZM5.31288 0C2.52414 0.00875889 0.5 2.26889 0.5 4.78826C0.5 6.00188 0.949566 7.10829 1.69119 7.95492C2.14321 8.47011 2.84901 9.54727 3.11919 10.4557C3.12005 10.4625 3.12175 10.4698 3.12261 10.4771H7.50342C7.50427 10.4698 7.50598 10.463 7.50684 10.4557C7.77688 9.54727 8.48281 8.47011 8.93484 7.95492C9.67728 7.13181 10.1258 6.02703 10.1258 4.78826C10.1258 2.15486 7.9709 0.000106649 5.31288 0ZM7.94902 7.11267C7.52078 7.60079 6.99082 8.37878 6.6077 9.18794H4.02051C3.63739 8.37878 3.10743 7.60079 2.67947 7.11294C2.11997 6.47551 1.8126 5.63599 1.8126 4.78826C1.8126 3.09829 3.12794 1.31944 5.28827 1.3126C7.2435 1.3126 8.81315 2.88226 8.81315 4.78826C8.81315 5.63599 8.50688 6.47551 7.94902 7.11267ZM4.87534 2.18767C3.66939 2.18767 2.68767 3.16939 2.68767 4.37534C2.68767 4.61719 2.88336 4.81288 3.12521 4.81288C3.36705 4.81288 3.56274 4.61599 3.56274 4.37534C3.56274 3.6515 4.1515 3.06274 4.87534 3.06274C5.11719 3.06274 5.31288 2.86727 5.31288 2.62548C5.31288 2.38369 5.11599 2.18767 4.87534 2.18767Z"></path>
        </svg>
    },
    caution: {
      outerStyle: "border-amber-500/20 bg-amber-50/50 dark:border-amber-500/30 dark:bg-amber-500/10",
      innerStyle: "text-amber-900 dark:text-amber-200",
      calloutType: "warning",
      icon: <svg className="flex-none w-5 h-5 text-amber-400 dark:text-amber-300/80" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-label="Warning">
          <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
        </svg>
    },
    danger: {
      outerStyle: "border-red-500/20 bg-red-50/50 dark:border-red-500/30 dark:bg-red-500/10",
      innerStyle: "text-red-900 dark:text-red-200",
      calloutType: "danger",
      icon: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor" className="text-red-600 dark:text-red-400/80 w-4 h-4" aria-label="Danger">
          <path d="M17.1 292c-12.9-22.3-12.9-49.7 0-72L105.4 67.1c12.9-22.3 36.6-36 62.4-36l176.6 0c25.7 0 49.5 13.7 62.4 36L494.9 220c12.9 22.3 12.9 49.7 0 72L406.6 444.9c-12.9 22.3-36.6 36-62.4 36l-176.6 0c-25.7 0-49.5-13.7-62.4-36L17.1 292zm41.6-48c-4.3 7.4-4.3 16.6 0 24l88.3 152.9c4.3 7.4 12.2 12 20.8 12l176.6 0c8.6 0 16.5-4.6 20.8-12L453.4 268c4.3-7.4 4.3-16.6 0-24L365.1 91.1c-4.3-7.4-12.2-12-20.8-12l-176.6 0c-8.6 0-16.5 4.6-20.8 12L58.6 244zM256 128c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"></path>
        </svg>
    }
  };
  let variant = type;
  let gotInvalidVariant = false;
  if (!asideVariants.includes(type)) {
    gotInvalidVariant = true;
    variant = "danger";
  }
  const iconVariants = ["regular", "solid", "light", "thin", "sharp-solid", "duotone", "brands"];
  if (!iconVariants.includes(iconType)) {
    iconType = "regular";
  }
  return <>
      <div className={`callout my-4 px-5 py-4 overflow-hidden rounded-2xl flex gap-3 border ${asideComponents[variant].outerStyle}`} data-callout-type={asideComponents[variant].calloutType}>
        <div className="mt-0.5 w-4" data-component-part="callout-icon">
          {}
          {icon === "" ? asideComponents[variant].icon : <Icon icon={icon} iconType={iconType} size={14} />}
        </div>
        <div className={`text-sm prose min-w-0 w-full ${asideComponents[variant].innerStyle}`} data-component-part="callout-content">
          {gotInvalidVariant ? <p>
              <span className="font-bold">
                Invalid <code>type</code> passed!
              </span>
              <br />
              <span className="font-bold">Received: </span>
              {type}
              <br />
              <span className="font-bold">Expected one of: </span>
              {asideVariants.join(", ")}
            </p> : <>
              {title && <p className="font-bold">{title}</p>}
              {children}
            </>}
        </div>
      </div>
    </>;
};

## TON Connect manifest

Before installing and setting up the TON Pay SDK, the application must provide a TON Connect manifest, which is a JSON file that defines application metadata. Wallets use the [TON Connect manifest](/ecosystem/ton-connect/manifest) to discover the application.

## First payment

<Steps>
  <Step title="Install necessary libraries">
    <Tabs>
      <Tab title="React">
        ```bash theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
        # API
        npm i @ton-pay/api

        # UI (install separately from API)
        npm i @ton-pay/ui-react @tonconnect/ui-react
        ```
      </Tab>

      <Tab title="Vanilla JS">
        ```bash theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
        # API
        npm i @ton-pay/api

        # UI (install separately from API)
        npm i @ton-pay/ui @tonconnect/ui
        ```
      </Tab>
    </Tabs>

    <Aside type="tip">
      The UI package does not depend on the API. Install `@ton-pay/api` only when API helpers are required.
    </Aside>
  </Step>

  <Step title="Add TON Connect provider">
    TON Pay UI uses TON Connect UI for wallet communication.

    The application must be wrapped with `TonConnectUIProvider` and configured with an absolute URL to the TON Connect manifest. Add `TonConnectUIProvider` at the root of the application.

    ```tsx theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
    import { TonConnectUIProvider } from '@tonconnect/ui-react';
    import AppContent from "./AppContent";

    export default function App() {
      return (
        <TonConnectUIProvider
          manifestUrl="https://myapp.com/tonconnect-manifest.json">
          <AppContent />
        </TonConnectUIProvider>
      );
    }
    ```
  </Step>

  <Step title="Add a payment button">
    Add a `TonPayButton` and provide a handler. The handler uses `useTonPay` to connect a wallet if needed, send a transaction through TON Connect, and return tracking data for the next step.

    * `TonPayButton` wraps wallet connect and disconnect UX and invokes the provided handler.
    * `useTonPay` accepts an async message factory that receives `senderAddr` and returns `{ message }` along with any tracking fields to propagate.
    * Return `reference` from `createTonPayTransfer` so it can be used later with `getTonPayTransferByReference`.

    The returned `{ message }` is a TON Connect transaction message. `useTonPay` forwards it to the wallet through TON Connect and initiates the transaction send; direct calls to the wallet SDK are not required.

    In the examples below, replace `<WALLET_ADDRESS>` with the recipient wallet address, `<TONPAY_API_KEY>` with an optional dashboard API key, and `<ORDER_REFERENCE>` with an order label or ID.

    ```tsx theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
    import { TonPayButton, useTonPay } from "@ton-pay/ui-react";
    import { createTonPayTransfer } from "@ton-pay/api";

    const recipientAddr = "<WALLET_ADDRESS>";
    const orderReference = "<ORDER_REFERENCE>";

    // Set chain to "mainnet" in production.
    const options = {
      chain: "testnet",

      // Pass an API key from the dashboard when available.
      apiKey: "<TONPAY_API_KEY>",
    } as const;

    export default function PayButton() {
      const { pay } = useTonPay();

      async function createMessage(senderAddr: string) {
        const { message, reference } = await createTonPayTransfer(
          {
            amount: 12.34,
            asset: "TON",
            recipientAddr,
            senderAddr,
            commentToSender: orderReference,
          },
          options
        );
        return { message, reference };
      }

      return (
        <TonPayButton handlePay={() => pay(createMessage)} />
      );
    }
    ```

    <Aside type="danger" title="Client-only processing is fragile">
      Users can close the tab before transaction results are persisted, which breaks tracking and confirmation logic. Build messages and track transactions in the [Send payments using React](/ecosystem/ton-pay/payment-integration/payments-react) guide.
    </Aside>

    <AccordionGroup>
      <Accordion title="Minimal server-side flow example">
        ```ts theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
        // Backend: POST /api/create-payment
        import { createTonPayTransfer, TON } from "@ton-pay/api";

        const recipientAddr = "<WALLET_ADDRESS>";

        // Set chain to "mainnet" in production.
        const options = {
          chain: "testnet",

          // Pass an API key from the dashboard when available.
          apiKey: "<TONPAY_API_KEY>",
        } as const;

        app.post("/api/create-payment", async (req, res) => {
          const { productId, senderAddr } = req.body;

          // Create an order and calculate the amount from the product price.
          const amount = 12.23;
          const orderId = 1;

          // Create the transfer and get tracking identifiers.
          const { message, reference, bodyBase64Hash } = await createTonPayTransfer(
            { amount, asset: TON, recipientAddr, senderAddr },
            options
          );

          // Persist identifiers in the database immediately.

          // Return only the message to the client.
          res.json({ message });
        });
        ```

        ```tsx theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
        // Frontend
        import { TonPayButton, useTonPay } from "@ton-pay/ui-react";

        export function PayOrder({ productId }: { productId: string }) {
          const { pay } = useTonPay();

          async function createMessage(senderAddr: string) {
            const response = await fetch("/api/create-payment", {
              method: "POST",
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify({ productId, senderAddr }),
            });
            const { message } = await response.json();
            return { message };
          }

          return <TonPayButton handlePay={() => pay(createMessage)} />;
        }
        ```
      </Accordion>
    </AccordionGroup>

    <Aside>
      The TON Pay API key is optional. If available from the dashboard, pass it to `createTonPayTransfer` options.
    </Aside>
  </Step>

  <Step title="Handle loading state and results">
    Wallet approval does not mean the payment is finalized yet. After `pay(createMessage)` resolves, use the `reference` returned by `createTonPayTransfer` to query TON Pay until the transfer leaves the `pending` state.

    The example below uses React. The same flow applies in other clients: keep the `reference`, poll status, and update the UI when the transfer leaves `pending`.

    Use the SDK flow below:

    1. Return `reference` from the `createMessage` function together with `message`.
    2. Call `pay(createMessage)` and, once it resolves, read the propagated `reference` from its return value.
    3. Call `getTonPayTransferByReference(reference, options)` with the same `chain` and optional `apiKey` used during transfer creation.
    4. While the SDK returns `status: "pending"`, keep the UI in a loading or "confirming payment" state.
    5. When the status becomes `success`, show the confirmation UI and persist any result fields the application needs, such as `txHash` or `traceId`.
    6. When the status becomes `error`, show the failure state and capture `errorCode` or `errorMessage` for diagnostics.

    In a client-only flow, persist the `reference` after `pay(createMessage)` resolves and returns it. This is enough to resume status checks after a reload during polling, but it does not cover the period while the wallet approval screen is open. To avoid that gap, create the transfer on the server and persist the `reference` before opening the wallet.

    ```tsx title="Result handling example" expandable theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
    import { useState } from "react";
    import { TonPayButton, useTonPay } from "@ton-pay/ui-react";
    import {
      createTonPayTransfer,
      getTonPayTransferByReference,
      type CompletedTonPayTransferInfo,
    } from "@ton-pay/api";

    type PaymentState = "idle" | "sending" | "pending" | "success" | "error";

    const amount = 12.34;
    const recipientAddr = "<WALLET_ADDRESS>";

    // Set chain to "mainnet" in production.
    const options = {
      chain: "testnet",

      // Pass an API key from the dashboard when available.
      apiKey: "<TONPAY_API_KEY>",
    } as const;

    export default function Checkout() {
      const { pay } = useTonPay();
      const [paymentState, setPaymentState] = useState<PaymentState>("idle");
      const [reference, setReference] = useState<string | null>(null);
      const [result, setResult] = useState<CompletedTonPayTransferInfo | null>(null);
      const [errorMessage, setErrorMessage] = useState<string | null>(null);

      async function createMessage(senderAddr: string) {
        const { message, reference } = await createTonPayTransfer(
          {
            amount,
            asset: "TON",
            recipientAddr,
            senderAddr,
          },
          options
        );

        setReference(reference);
        return { message, reference };
      }

      // Polls TON Pay until the transfer gets a final status.
      async function waitForTransferResult(reference: string) {
        for (;;) {
          const transfer = await getTonPayTransferByReference(reference, options);

          if (transfer.status === "pending") {
            await new Promise((resolve) => setTimeout(resolve, 1000));
            continue;
          }

          return transfer;
        }
      }

      async function handlePay() {
        setPaymentState("sending");
        setErrorMessage(null);
        setReference(null);
        setResult(null);

        try {
          const { reference } = await pay(createMessage);
          setPaymentState("pending");

          const transfer = await waitForTransferResult(reference);

          if (transfer.status === "success") {
            setResult(transfer);
            setPaymentState("success");
            return;
          }

          setPaymentState("error");
          setErrorMessage(transfer.errorMessage ?? "Payment failed");
        } catch (error) {
          setPaymentState("error");
          setErrorMessage(error instanceof Error ? error.message : "Payment failed");
        }
      }

      return (
        <>
          <TonPayButton
            handlePay={handlePay}
            isLoading={paymentState === "sending" || paymentState === "pending"}
          />

          {paymentState === "pending" && reference && (
            <div>
              Payment submitted. Waiting for blockchain confirmation. Reference: {reference}
            </div>
          )}

          {paymentState === "success" && result && (
            <div>
              Payment confirmed. Tx hash: {result.txHash}
            </div>
          )}

          {paymentState === "error" && errorMessage && (
            <div>
              Payment failed: {errorMessage}
            </div>
          )}
        </>
      );
    }
    ```

    The [status lookup guide](/ecosystem/ton-pay/payment-integration/status-info) describes the response fields and lookup methods used in this step.
  </Step>
</Steps>

## Full example

This minimal example scaffolds a React app, installs TON Pay dependencies, and renders a working button wired to TON Connect. Replace the manifest URL and `recipientAddr` with the necessary values.

```bash theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
npx create-react-app my-app --template typescript
cd my-app
npm i @ton-pay/api @ton-pay/ui-react @tonconnect/ui-react
```

```tsx theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
// src/App.tsx
import { TonConnectUIProvider } from "@tonconnect/ui-react";
import { TonPayButton, useTonPay } from "@ton-pay/ui-react";
import { createTonPayTransfer } from "@ton-pay/api";

const recipientAddr = "<WALLET_ADDRESS>";
const commentToSender = "Order #123";

// Set chain to "mainnet" in production.
const options = {
  chain: "testnet",

  // Pass an API key from the dashboard when available.
  apiKey: "<TONPAY_API_KEY>",
} as const;

function AppContent() {
  const { pay } = useTonPay();

  async function createMessage(senderAddr: string) {
    const { message, reference } = await createTonPayTransfer(
      {
        amount: 12.34,
        asset: "TON",
        recipientAddr,
        senderAddr,
        commentToSender,
      },
      options
    );
    return { message, reference };
  }

  return (
    <TonPayButton handlePay={() => pay(createMessage)} />
  );
}

export default function App() {
  return (
    <TonConnectUIProvider
      manifestUrl="https://ton-connect.github.io/demo-dapp-with-wallet/tonconnect-manifest.json"
    >
      <AppContent />
    </TonConnectUIProvider>
  );
}
```

## See also

* [Build a transfer](/ecosystem/ton-pay/payment-integration/transfer)
* [Send payments using React](/ecosystem/ton-pay/payment-integration/payments-react)
* [Webhooks](/ecosystem/ton-pay/webhooks)
* [API reference](/ecosystem/ton-pay/api-reference)
