# Add Frak to a custom website

# Add Frak to a custom website

For a custom-built site, adding Frak takes two parts:

1. **Add the Frak components to your site.** Load Frak, set one config object, and drop in the share button, the welcome banner, and the post-purchase card. Pick the setup that matches your stack below.
2. **Validate purchases from your backend.** Confirm real orders with a signed webhook, so rewards only go out on genuine sales.
**Before you start:** Register your site and add your domain in the [business dashboard](https://business.frak.id/) first. Your merchant ID is then resolved automatically from your domain, so the config below stays short. See [Register your site](/guides/dashboard/register/).

## The components

All three components are framework-agnostic [web components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components), so they work the same whether you write plain HTML, use a bundler, or build with React.

| Component | Where it goes | What it does |
| --- | --- | --- |
| `<frak-button-share>` | Product page, homepage | Lets customers share your store and earn rewards |
| `<frak-banner>` | Top of the page | Welcomes referred visitors |
| `<frak-post-purchase>` | Order confirmation page | Prompts a share right after checkout, and tracks the order |
**Attribute names use dashes:** In HTML and JSX, multi-word attributes must be written with dashes, not camelCase: use `customer-id`, not `customerId`. Browsers lowercase attribute names, so a camelCase attribute is silently ignored.

## 1. Add Frak to your site

### Load and configure Frak

Add this to the `<head>` of your pages. The config object is read by Frak when it loads, so set it **before** the script tag.

```html title="index.html"
<head>
  <!-- 1. Configure Frak (domain defaults to the current host) -->
  <script>
    window.FrakSetup = {
      config: {
        metadata: {
          name: "Your Store",
          currency: "eur",
        },
      },
    };
  </script>

  <!-- 2. Avoid a flash of unstyled elements while the script loads -->
  <style>
    frak-button-share:not(:defined),
    frak-banner:not(:defined),
    frak-post-purchase:not(:defined) { display: none !important; }
  </style>

  <!-- 3. Load the components (auto-registers them and boots the SDK) -->
  <script
    type="module"
    src="https://cdn.jsdelivr.net/npm/@frak-labs/components@latest"
    defer="defer"
  ></script>
</head>
```

### Add the components

Place the banner near the top of your `<body>`, and the share button wherever you want customers to share:

```html
<body>
  <!-- Welcomes referred visitors -->
  <frak-banner></frak-banner>

  <!-- Inherits your theme's .button styles via classname -->
  <frak-button-share classname="button"></frak-button-share>
</body>
```

### Track the purchase

On your order confirmation page, add the post-purchase card with your order details. When `customer-id`, `order-id`, and `token` are all present, the card also registers the order with Frak automatically:

```html
<frak-post-purchase
  customer-id="cust_123"
  order-id="order_456"
  token="a-unique-order-token"
></frak-post-purchase>
```

If you do not want to show the card, register the order directly instead. The action becomes available once Frak is ready:

```html
<script>
  window.addEventListener("frak:client", () => {
    window.FrakSetup.core.trackPurchaseStatus({
      customerId: "cust_123",
      orderId: "order_456",
      token: "a-unique-order-token",
    });
  });
</script>
```

### Install

```bash
npm install @frak-labs/components @frak-labs/core-sdk
```

### Configure Frak

Set the config in its own module so it runs first:

```ts title="frak-setup.ts"
import type { FrakWalletSdkConfig } from "@frak-labs/core-sdk";

declare global {
  interface Window {
    FrakSetup: { config?: FrakWalletSdkConfig };
  }
}

window.FrakSetup = {
  config: {
    metadata: {
      name: "Your Store",
      currency: "eur",
    },
  },
};
```

### Register the components

Import your config module first, then await trackPurchaseStatus({
  customerId: "cust_123",
  orderId: "order_456",
  token: "a-unique-order-token",
});
```

The visual components are the same web components, used inside JSX. Install them and (optionally) `@frak-labs/core-sdk` for direct action calls:

```bash
npm install @frak-labs/components @frak-labs/core-sdk
```

### Configure and register

Set the config in its own module, then import it (and the components) before you render your app:

```ts title="frak-setup.ts"
declare global {
  interface Window {
    FrakSetup: { config?: FrakWalletSdkConfig };
  }
}

window.FrakSetup = {
  config: {
    metadata: {
      name: "Your Store",
      currency: "eur",
    },
  },
};
```

```tsx title="main.tsx"
createRoot(document.getElementById("root")!).render(<App />);
```

### Use the components in JSX

```tsx title="App.tsx"
export function App() {
  return (
    <>
      <frak-banner />
      <frak-button-share classname="button" />

      {/* On your order confirmation route */}
      <frak-post-purchase
        customer-id="cust_123"
        order-id="order_456"
        token="a-unique-order-token"
      />
    </>
  );
}
```

The post-purchase card tracks the order automatically. To track without the card, call `trackPurchaseStatus` from `@frak-labs/core-sdk/actions` after the order is placed.
**TypeScript:** TypeScript may not recognize the `<frak-*>` tags in JSX. Add a small declaration so it does:

```ts title="frak.d.ts"
declare global {
  namespace JSX {
    interface IntrinsicElements {
      "frak-banner": HTMLAttributes<HTMLElement>;
      "frak-button-share": HTMLAttributes<HTMLElement> & { classname?: string; text?: string };
      "frak-post-purchase": HTMLAttributes<HTMLElement> & {
        "customer-id"?: string;
        "order-id"?: string;
        token?: string;
      };
    }
  }
}
```

On React 19, declare the same `namespace JSX` inside `declare module "react"` instead of `declare global`.
**Need programmatic control?:** For custom flows (wallet status, referral hooks, opening the modal yourself), `@frak-labs/react-sdk` exposes React hooks and providers. See the [React integration guide](/developers/integration/react/).
**Every option:** The full attribute list for each component lives in the developer reference: [share button](/developers/components/share-button/), [banner](/developers/components/banner/), [post-purchase](/developers/components/post-purchase/), and the [`FrakSetup` config](/developers/components/frak-setup/).

## 2. Validate purchases from your backend

Tracking on the page tells Frak an order *might* be coming. Rewards only fire once your backend confirms the order is real with a signed webhook. This keeps rewards tied to genuine, paid sales.

1. **The page registers the order.** The post-purchase card (or `trackPurchaseStatus`) sends `customerId`, `orderId`, and `token` so Frak starts listening for that order.

2. **Your backend confirms it.** When the order is paid (or refunded, cancelled), your server sends a webhook to Frak with the same identifiers and an HMAC signature.

3. **Frak triggers the reward.** Once the signature checks out and the status is `confirmed`, Frak sends the `PurchaseCompleted` interaction, which can pay out rewards based on your active campaigns.

Your webhook URL and signing secret are in the [business dashboard](https://business.frak.id/), under the **Purchase Tracker** section of your merchant. Sign the entire request body with HMAC SHA-256 and send it in the `x-hmac-sha256` header.

```ts
async function sendPurchaseWebhook(order: {
  id: string;
  customerId: string;
  status: "pending" | "confirmed" | "cancelled" | "refunded";
  token: string;
  currency?: string;
  totalPrice?: string;
}) {
  const body = JSON.stringify(order);
  const hmac = crypto
    .createHmac("sha256", process.env.FRAK_WEBHOOK_SECRET)
    .update(body)
    .digest("hex");

  await fetch(process.env.FRAK_WEBHOOK_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-hmac-sha256": hmac,
      // Use "true" while testing, "false" in production
      "x-test": "false",
    },
    body,
  });
}

await sendPurchaseWebhook({
  id: "order_456",
  customerId: "cust_123",
  status: "confirmed",
  token: "a-unique-order-token",
  currency: "EUR",
  totalPrice: "99.99",
});
```

```php
function sendPurchaseWebhook(array $order): void {
    $url = getenv('FRAK_WEBHOOK_URL');
    $secret = getenv('FRAK_WEBHOOK_SECRET');

    $body = json_encode($order);
    $hmac = hash_hmac('sha256', $body, $secret);

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $body,
        CURLOPT_HTTPHEADER => [
            'Content-Type: application/json',
            'x-hmac-sha256: ' . $hmac,
            'x-test: false', // "true" while testing
        ],
    ]);
    curl_exec($ch);
    curl_close($ch);
}

sendPurchaseWebhook([
    'id' => 'order_456',
    'customerId' => 'cust_123',
    'status' => 'confirmed',
    'token' => 'a-unique-order-token',
    'currency' => 'EUR',
    'totalPrice' => '99.99',
]);
```
**The identifiers must match:** The webhook's `id` is the same value as the `order-id` on the page, and `customerId` and `token` must match the values you sent when tracking. If they differ, Frak cannot link the confirmation to the order and no reward fires.

See the [Purchase webhook reference](/developers/api/webhook/) for the full payload (including line items) and the [track purchase endpoint](/developers/api/track-purchase/) for the page-side call.

## Next steps

[Add funds](/guides/dashboard/configure/funds/)
  [Create a campaign](/guides/campaigns/create/)
  [Components reference](/developers/components/)
  [Vanilla JS / CDN guide](/developers/integration/cdn/)