Integrations

How conversion tracking works, and everything your account needs to run it.

Set up web3 tracking

~10 min setup

On-chain conversions are detected for you — what you integrate is the link between the visitor's wallet and the creator who sent them. Four steps.

Detected does not mean automatic

We watch your contract and see the on-chain action, but the chain does not say who referred the wallet that performed it. Tell us which wallet belongs to which referral and the creator gets credited; skip it and the conversion is detected, unattributed and never paid out.

The values in angle brackets are placeholders. Sign in and the guide fills in your real endpoints and tokens.
1

Get a public token

Wallet bindings are keyed by the same public token as the browser pixel — a browser key identifying your company, visible in your page source by design and re-viewable any time.

2

Install the pixel to capture the ref id

Paste this on every page. It stores the ref id from the creator's link in a first-party cookie — the binding in the next step reads it from there, so nothing works without it.

site-wide-snippet.html
<script>
  !(function(w,d){w.influence360=w.influence360||function(){(w.influence360.q=w.influence360.q||[]).push(arguments)};var s=d.createElement('script');s.async=1;s.src='https://tracking-pixel.influence360.io/v1/influence360.js';d.head.appendChild(s)})(window,document);

  // Run on every page — captures the ref id from the destination URL into a first-party cookie.
  influence360('init', '<YOUR_PUBLIC_TOKEN>', { collectUrl: 'https://tracking.influence360.io/public/pixel/conversion' });
</script>
3

Bind the wallet when it connects

One call, in your wallet-connect handler. It is fire-and-forget, never throws into your page, and does nothing when no referral was captured — so it is safe to call unconditionally.

  1. Call influence360('identify', address, …) as soon as a wallet connects, and again whenever the connected account changes.
  2. Pass the chain of the connected wallet. It is optional metadata for a plain binding, but required if you later add signatures — it selects how the signature is verified.
  3. Re-binding the same wallet to the same referral is harmless; repeats are deduplicated. A wallet that later arrives through a different creator's link is re-attributed to the most recent referral.
ETHEREUM
bind-wallet.js
// Run once the visitor connects a wallet (and again on account switch).
async function onWalletConnected(address) {
  influence360('identify', '<CONNECTED_WALLET_ADDRESS>', { chain: 'ETHEREUM' });
}

Verify with a test binding

Add the test flag while wiring this up. A test binding runs the same validation and shows up in Test events on the Configuration tab, but is never used to credit a payout.

bind-wallet-test.js
// Run once the visitor connects a wallet (and again on account switch).
async function onWalletConnected(address) {
  influence360('identify', '<CONNECTED_WALLET_ADDRESS>', { chain: 'ETHEREUM', test: true });
}

Signed bindings (hardening)

A signed binding carries a wallet signature proving the visitor actually controls the address, so nobody can bind a wallet they do not own. Worth adding for every integration: the default attribution strictness already prefers signed bindings over unsigned ones for the same wallet, and Require signed accepts nothing else. Unsigned bindings keep working either way — this only decides which one wins.

The pixel builds the message and sends the binding, but it will not sign for you — the signature has to come from the visitor's own wallet, and how you request one differs per chain and wallet library. So you produce the signature and hand it to the same identify call.
The message to sign

The wallet must sign exactly this string — line breaks included, no trailing newline, and issuedAt equal to the signedAtMs you send. Any deviation and the binding is stored as unsigned. The timestamp must be within about 10 minutes of our server time.

message.md
influence360 wallet attribution
ref:<REF_ID>
wallet:<CONNECTED_WALLET_ADDRESS>
chain:ETHEREUM
issuedAt:1700000000000
Producing a signed binding
signed-binding.js
// A signed binding proves the visitor controls the wallet. Required when your attribution
// strictness is "Require signed"; it also wins over an unsigned binding under "Prefer signed".
async function onWalletConnected(address) {
  const signedAtMs = Date.now();

  // 1. Ask the pixel for the exact message to sign — never hand-roll it, a single character of
  //    difference and the signature will not verify (the binding is kept, but unsigned).
  const message = influence360.bindingMessage(address, 'ETHEREUM', signedAtMs);
  if (!message) return; // no referral captured — nothing to bind

  // 2. Sign it with the wallet client you already have. EVM shown; on Solana sign the same UTF-8
  //    bytes with ed25519 and hex-encode the 64-byte signature.
  const signature = await window.ethereum.request({
    method: 'personal_sign',
    params: [message, address],
  });

  // 3. Send the binding with the proof attached. Pass the SAME signedAtMs you signed with.
  influence360('identify', address, { chain: 'ETHEREUM', signature, signedAtMs });
}

Signature verification is supported on: ETHEREUM, POLYGON, ARBITRUM, BASE, BSC, AVALANCHE, OPTIMISM, MANTLE, SOLANA. Bindings on TRON are accepted but always unsigned.

The binding endpoint

Bind from something other than our pixel — a mobile wallet flow or your own script — by posting the same body yourself. The public token goes on the query string, not in the body.

FieldRequirementDescription
refIdRequiredThe ref id the visitor arrived with, from the pixel's first-party cookie. The binding is tied to this referral.
walletRequiredThe wallet address the visitor connected — the address we match against on-chain activity.
chainOptionalThe chain of the connected wallet. Metadata for a plain binding; required when you send a signature, as it selects the verification scheme.
signatureOptionalHex wallet signature over the canonical binding message, proving control of the address. Absent or invalid means the binding is kept but unsigned.
signedAtMsOptionalEpoch milliseconds the signature was issued — the same value used as issuedAt in the signed message. Required whenever a signature is sent.
testOptionalMarks a canary binding — validated the same way, never used to credit a payout.
bind.sh
curl -X POST 'https://tracking.influence360.io/public/pixel/identify?publicToken=<YOUR_PUBLIC_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "refId": "<REF_ID>",
    "wallet": "<CONNECTED_WALLET_ADDRESS>",
    "chain": "ETHEREUM"
  }'
Binding from browser JavaScriptA backend, a script or a native mobile wallet flow can post the JSON above as-is. From browser JavaScript the content type has to change: send text/plain;charset=UTF-8, not application/json. Only a few content types are allowed through a cross-origin POST without a preflight, and the collector does not answer preflights — send JSON from a page and the binding never arrives, with nothing logged and the same empty success returned.
bind-wallet.js
// Run once the visitor connects a wallet (and again on account switch).
async function bindWallet(address) {
  var refId = influence360RefId();   // your own reader — see the pixel guide's "Your own code" tab
  if (!refId) return;                // no referral captured, nothing to bind

  var url = 'https://tracking.influence360.io/public/pixel/identify?publicToken=<YOUR_PUBLIC_TOKEN>';
  var body = JSON.stringify({ refId: refId, wallet: address, chain: 'ETHEREUM' });

  // 'text/plain;charset=UTF-8' is CORS-safelisted, so this cross-origin POST skips the
  // preflight. application/json triggers one that is never answered — the binding is lost with no
  // error you can see, and the endpoint answers 204 either way.
  if (navigator.sendBeacon) {
    var blob = new Blob([body], { type: 'text/plain;charset=UTF-8' });
    if (navigator.sendBeacon(url, blob)) return;
  }
  fetch(url, {
    method: 'POST',
    body: body,
    headers: { 'Content-Type': 'text/plain;charset=UTF-8' },
    keepalive: true,
    mode: 'no-cors',
  }).catch(function () {});
}

Responses are always empty with no error detail, so a prober learns nothing: a binding from a domain that is not allow-listed on the token is dropped exactly like a valid one. If bindings are not landing, check the token's allowed domains and the Test events feed.

Or delegate the work

Integrate with an AI assistant

Prefer to delegate? Paste this prompt into your AI coding assistant — it describes the wallet binding end to end: where to call it, the endpoint, and the exact message to sign.

Select your client

Run `claude` in your project, then paste this prompt.

integration-prompt.md
I'm integrating Influence360 CPA web3 conversion tracking into my dApp. Generate the code for my stack and explain where each piece goes.

CONTEXT
- An influencer link sends a visitor to my site with an Influence360 ref id in the destination URL.
- On-chain conversions are DETECTED by Influence360 watching the contract configured on the campaign action — I never report them:
   - WALLET_ACTIVITY — a wallet interacted with the configured contract
   - TOKEN_ACQUISITION — a wallet acquired at least the configured minimum of the token
   - DEPOSIT_STAKE — a wallet deposited/staked at least the configured minimum
   - NFT_MINT — a wallet minted from the configured NFT collection
- What I DO have to integrate: binding the wallet the visitor connects to the referral they arrived with. Without that binding an on-chain action from that wallet has nothing to attribute to and the creator is never credited. This is the whole web3 integration.

SETUP — load the pixel (influence360.js) once on every page; it captures and persists the ref id:
   <script>
     !(function(w,d){w.influence360=w.influence360||function(){(w.influence360.q=w.influence360.q||[]).push(arguments)};var s=d.createElement('script');s.async=1;s.src='https://tracking-pixel.influence360.io/v1/influence360.js';d.head.appendChild(s)})(window,document);
     influence360('init', '<YOUR_PUBLIC_TOKEN>', { collectUrl: 'https://tracking.influence360.io/public/pixel/conversion' });
   </script>

BIND — call this as soon as a wallet is connected, and again whenever the account changes:
   influence360('identify', '<connected-wallet-address>', { chain: '<CHAIN>' });

   Wire it into whatever my wallet library exposes (wagmi useAccount / RainbowKit / Reown-WalletConnect
   session, ethers provider 'accountsChanged', Solana wallet-adapter onConnect, …). It is fire-and-forget
   and never throws into the page. It is a no-op if no ref id was captured, so it is safe to call always.

SIGNED BINDINGS (recommended; required if my Influence360 attribution strictness is "Require signed")
- A plain identify call produces an UNSIGNED binding. A signed one proves the visitor controls the wallet:
  it wins over an unsigned binding for the same wallet under "Prefer signed" (the default), and is the ONLY
  kind that counts under "Require signed". Add the signature to the same call:
     const signedAtMs = Date.now();
     const message = influence360.bindingMessage(address, '<CHAIN>', signedAtMs);  // '' when no referral
     const signature = await window.ethereum.request({ method: 'personal_sign', params: [message, address] });
     influence360('identify', address, { chain: '<CHAIN>', signature, signedAtMs });
- Always build the message with influence360.bindingMessage(...) — do NOT hand-roll it. It must match the
  server's reconstruction byte-for-byte or the signature silently fails to verify and the binding lands
  unsigned. Pass the SAME signedAtMs to bindingMessage and to identify; it is signed into the message.
- EVM: personal_sign (EIP-191), 65-byte signature as hex.
  Solana: sign the same UTF-8 message bytes with ed25519 and hex-encode the 64-byte signature.
- signedAtMs must be within ~10 minutes of server time or the signature is treated as stale.
- Building the request myself instead of using the pixel? POST the same body to
  https://tracking.influence360.io/public/pixel/identify?publicToken=<YOUR_PUBLIC_TOKEN> as
  { "refId": "<ref id>", "wallet": "<address>", "chain": "<CHAIN>", "signature": "<hex>", "signedAtMs": <epoch ms> }

NOTES
- The ref id comes from the _influence360_ref_id first-party cookie the pixel writes; read it from there if I build the request myself.
- Signature verification supports: ETHEREUM, POLYGON, ARBITRUM, BASE, BSC, AVALANCHE, OPTIMISM, MANTLE, SOLANA. TRON bindings are accepted but can only ever be UNSIGNED, so they don't work under "Require signed".
- The public token is not a secret (it ships in the page), but bindings are only accepted from the domains allow-listed on that token — add my dApp's domain there.
- Add "test": true (or { test: true } on the pixel call) to fire a canary binding while wiring this up — validated the same way, never used to credit a payout.
- Re-binding the same wallet to the same referral is harmless; it is deduplicated.