> For the complete documentation index, see [llms.txt](https://docs.fuul.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.fuul.xyz/developer-guide/tracking-referrals-in-your-app.md).

# Tracking Referrals

For Fuul to attribute conversions to the right referrer, your app must send two tracking events: **`pageview`** and **`connect_wallet`** (via `identifyUser`).

{% hint style="info" %}
These events require the `send:tracking_event` API key. See [API Key Management](/developer-guide/api-key-management.md).
{% endhint %}

## How attribution works

```
User clicks referral link → pageview event → user connects wallet → identifyUser event
                                   ↓                                        ↓
                         Captures referrer info                Links wallet to tracking session
                                   ↓                                        ↓
                            Later, user converts → Fuul attributes conversion to the referrer
```

## 1. Send pageview

Call `sendPageview` on every page load. The SDK automatically captures the referrer's affiliate code from the URL (`?af=code`, or the legacy `?referrer=code`) and stores a tracking ID in localStorage.

```typescript
import { Fuul } from '@fuul/sdk';

await Fuul.sendPageview();
```

{% hint style="info" %}
The SDK reads referral parameters from the URL automatically. You don't need to persist them when navigating between pages.
{% endhint %}

## 2. Identify the user

Call `identifyUser` every time a user connects a wallet or logs in — including when they switch wallets during a session. This sends a `connect_wallet` event that links their identity to the tracking session.

```typescript
import { Fuul } from '@fuul/sdk';

await Fuul.identifyUser({
  identifier: '0x1234...',
  identifierType: 'evm_address', // evm_address | solana_address | xrpl_address | sui_address | stellar_address | email
  signature: '0xabc...',
  message: 'Sign to verify your identity',
});
```

### Smart contract wallets

For smart contract wallets (EIP-1271), add the `accountChainId` parameter:

```typescript
await Fuul.identifyUser({
  identifier: '0x1234...',
  identifierType: 'evm_address',
  signature: '0xabc...',
  message: 'Sign to verify your identity',
  accountChainId: 1, // Chain ID where the smart contract wallet is deployed
});
```

Supported chains for smart contract wallets:

| Network    | Chain ID |
| ---------- | -------- |
| Ethereum   | 1        |
| Arbitrum   | 42161    |
| Optimism   | 10       |
| Base       | 8453     |
| Polygon    | 137      |
| zkSync Era | 324      |
| BNB Chain  | 56       |
| Avalanche  | 43114    |
| Mode       | 34443    |
| Abstract   | 2741     |
| Bob        | 60808    |
| Berachain  | 80094    |

### XRPL signatures

For XRPL wallets, include the `signaturePublicKey`:

```typescript
await Fuul.identifyUser({
  identifier: 'rAddress...',
  identifierType: 'xrpl_address',
  signature: '0xabc...',
  signaturePublicKey: '0x1234...',
  message: 'Sign to verify your identity',
});
```

### Stellar signatures

Stellar wallets identify with a `stellar_address` on **pubnet**. Proof of possession is a [SEP-53](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0053.md) message signature, not an EVM-style one, so it will not verify through viem or wagmi.

```typescript
await Fuul.identifyUser({
  identifier: 'GABC...',
  identifierType: 'stellar_address',
  signature: '...',           // SEP-53 message signature
  message: 'Sign to verify your identity',
});
```

**Address rules:**

| Prefix | Accepted | Notes                                                                     |
| ------ | -------- | ------------------------------------------------------------------------- |
| `G…`   | Yes      | Standard ed25519 public key StrKey                                        |
| `M…`   | No       | Muxed accounts are rejected                                               |
| `C…`   | No       | Contract addresses are rejected. See the note on `stellar_contract` below |
| `S…`   | No       | Secret keys are rejected, and must never be sent anywhere                 |

{% hint style="warning" %}
`@fuul/sdk` exports a seventh `UserIdentifierType` value, `StellarContract` (`'stellar_contract'`), which you must never pass as a user identifier. It exists for enum parity with the currency side of the API, where a `C…` StrKey names a Soroban token rather than a person. Passing it to an identify, attribution, or payout endpoint returns a **500**, not a validation error.

The six values in the lists on this page are the complete set of valid user identifier types. For denominating an event's value in a Stellar asset, see [Sending Custom Events](/developer-guide/sending-custom-events-through-the-api.md#stellar-assets).
{% endhint %}

{% hint style="warning" %}
Stellar addresses are **case-sensitive** and are never lowercased by Fuul. Unlike EVM addresses, `GABC…` and `gabc…` are not the same user. Pass the address exactly as the wallet returns it, or you will create a second, unmatched identity.
{% endhint %}

{% hint style="warning" %}
Stellar is supported for **identification and attribution only**. A Stellar address can accept referral codes, be attributed conversions, appear on leaderboards, and earn points. It cannot receive an onchain token payout: a payout targeting a `stellar_address` fails with `UnsupportedIdentifierType`.

For programs paying tokens, use points for Stellar users, or collect an EVM or Solana payout address separately.
{% endhint %}

On the [Fuul Hosted Hub](https://github.com/fuul-automation/gitbook-docs/tree/dev/incentives-hub/no-code.md), Stellar wallet connection (Freighter, Lobstr, and others) is handled for you. No extra integration work is needed.

## 3. Send frontend events (optional)

Beyond `pageview` and `connect_wallet`, the SDK can send arbitrary named events from the browser with `sendEvent`. Use it for frontend interactions you want recorded against the current tracking session — a button click, a completed form, a step in an onboarding flow.

```typescript
import { Fuul } from '@fuul/sdk';

await Fuul.sendEvent('waitlist_joined', { plan: 'pro' });
```

The signature is positional: an event name, then an optional `args` object. The SDK attaches the tracking ID from the current session automatically, so the event lands on the same tracking session as the pageview that started it.

{% hint style="warning" %}
**`sendEvent` cannot trigger a conversion.** It runs with a `send:tracking_event` key, which is not allowed to create trigger events. Anything that should produce a reward has to be sent from your backend with a `send:trigger_event` key — see [Sending Custom Events](/developer-guide/sending-custom-events-through-the-api.md).

`sendEvent` is browser-only. It throws outside a browser context, like `sendPageview` and `identifyUser`.
{% endhint %}

`sendPageview` also takes an optional page name if you want to override the default (`document.location.pathname`):

```typescript
await Fuul.sendPageview('/product/123');
```

## Sending identifyUser from the backend

If you identify users server-side, send the `connect_wallet` event via the REST API. You'll need the `tracking_id` from the browser's `localStorage` (key: `fuul.tracking_id`) — it must match the one sent in the pageview event.

```python
import requests

url = "https://api.fuul.xyz/api/v1/events"

payload = {
    "metadata": {
        "tracking_id": "trackingId123"
    },
    "name": "connect_wallet",
    "user": {
        "identifier": "0x1234...",
        "identifier_type": "evm_address"  # evm_address | solana_address | xrpl_address | sui_address | stellar_address | email | uuid
    },
    "signature": "0xabc...",
    "signature_message": "Sign to verify your identity",
    "account_chain_id": 1  # Only for smart contract wallets
}

headers = {
    "content-type": "application/json",
    "authorization": "Bearer your-api-key"
}

response = requests.post(url, json=payload, headers=headers)
```

**cURL:**

```bash
curl -X POST https://api.fuul.xyz/api/v1/events \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-api-key" \
  -d '{
    "metadata": { "tracking_id": "trackingId123" },
    "name": "connect_wallet",
    "user": {
      "identifier": "0x1234...",
      "identifier_type": "evm_address"
    },
    "signature": "0xabc...",
    "signature_message": "Sign to verify your identity"
  }'
```

{% hint style="info" %}
Valid `identifier_type` values: `evm_address` | `solana_address` | `xrpl_address` | `sui_address` | `stellar_address` | `email` | `uuid`
{% endhint %}

## Signature types

Fuul supports two signature verification methods:

| Type                 | Verification                                                                 | Use case                |
| -------------------- | ---------------------------------------------------------------------------- | ----------------------- |
| Regular message      | [Viem verifyMessage](https://viem.sh/docs/actions/public/verifyMessage.html) | Standard EOA wallets    |
| Typed data (EIP-712) | [Viem verifyTypedData](https://viem.sh/docs/actions/public/verifyTypedData)  | Structured data signing |

For typed data signatures, stringify the typed data object:

```typescript
import { Fuul } from '@fuul/sdk';

const typedData = { address, domain, types, primaryType, message };

await Fuul.identifyUser({
  identifier: '0x1234...',
  identifierType: 'evm_address',
  message: JSON.stringify(typedData),
  signature: '0xabc...',
});
```

{% hint style="info" %}
Requiring users to sign a message ensures event validity and prevents attribution fraud. Mandatory for wallet-based identifiers; optional for `uuid`.
{% endhint %}
