# Influence360 conversion tracking — full documentation > How conversion tracking works, and everything your account needs to run it. This file is the complete Documentation tab of https://influence360.io/integrations as a single markdown document, generated from the same copy and code examples the page renders. Everything here is public and needs no account. ## Contents 1. Endpoints and placeholders 2. How tracking works 3. Set up the pixel 4. Set up the postback 5. Set up web3 tracking 6. Tokens & security ## 1. Endpoints and placeholders Every endpoint below is per-environment, not per-company — these are the real URLs. | Purpose | Endpoint | | --- | --- | | Browser pixel conversion | `POST https://tracking.influence360.io/public/pixel/conversion` | | Server postback conversion | `POST https://tracking.influence360.io/public/postback/conversion` | | Wallet binding (identify) | `POST https://tracking.influence360.io/public/pixel/identify` | | Pixel script | `https://tracking-pixel.influence360.io/v1/influence360.js` | Angle-bracket values in the examples are placeholders you replace with your own runtime values: | Placeholder | What to substitute | | --- | --- | | `` | A public token from the Tokens tab. A browser key, not a secret — it ships in your page source and is re-viewable any time. | | `` | A private token secret, shown once when you create or rotate it. Server-side only. | | `` | The ref id the visitor arrived with, read from the `influence360RefId` query parameter or the `_influence360_ref_id` first-party cookie. | | `` | The wallet address the visitor connected, from your dApp's wallet provider. | | `:` | Your own id for the conversion, used as `dedupKey` — prefixed with the event type: a key pays once per campaign and event type, and new-users-only actions compare it across all your event types (see the crediting rules in section 2). | ## 2. How tracking works Source: https://influence360.io/integrations ### In plain terms A creator shares a link to your site. Someone clicks it, and later does something you care about — signs up, buys something, makes a deposit. Your site tells us it happened, we work out which creator sent that person, and the creator gets paid for it. The rest of this page is how each of those steps works. ### How conversion tracking works The full path from a creator's link to a credited conversion — what runs on your site and what Influence360 does. #### Attribution — creator to landing page 1. **Creator shares link** _(Creator)_ — A unique go.influence360.io tracking link per creator. 2. **Click redirect** _(Influence360)_ — We record the click and send the visitor on to your site, tagged with a ref id — the code that says which creator sent them. 3. **Landing page** _(Browser)_ — Our snippet on your page saves that ref id in a cookie on your own domain. #### Conversion capture — your site reports the event 4. **Visitor converts** _(Your site)_ — Sign-up, purchase or other action on your site. 5. **Browser pixel** _(Your site)_ — A snippet on the page reports it from the visitor's browser. 6. **Server postback** _(Your backend)_ — Your own server reports it to us directly. #### Validation & payout — Influence360 takes over 7. **Influence360 attributes** _(Influence360)_ — We match the ref id to the creator, then run our timing, duplicate and fraud checks. 8. **Creator credited** _(Influence360)_ — Validated conversions feed dashboards and payouts. ### Attribution Every creator in a Performance (cost-per-action) campaign shares a unique tracking link. When a visitor clicks it, Influence360 records the click and forwards them to your destination URL. It adds a ref id to the web address — a short code identifying which creator sent that visitor. The pixel then stores the ref id in a cookie set on your own domain, so it is still there when the visitor converts. When the visitor converts, you report the conversion together with the ref id — from the browser via the pixel, or from your server via the postback. Influence360 matches the ref id back to the creator, then runs its checks before crediting the conversion. It has to fall inside the attribution window — the time limit after a click for a conversion to still count. It also has to be new rather than a repeat of one already reported, and it has to clear our fraud checks. ### Pixel or postback: which should you use? Two ways to tell us a conversion happened: the pixel is a snippet of code in your web page, the postback is a request your own server sends us. Both report the same events into the same attribution pipeline. What differs is what we can guarantee about the report itself — which is why anything carrying an amount belongs on your server. **Money events belong on your server** — Report Purchase and Deposit through the server postback. Sign-up, KYC verification, App install and Subscription carry no amount, so either route is fine — pick whichever is easier to reach in your stack. | What differs | Browser pixel | Server postback (recommended for money events) | | --- | --- | --- | | Best for | Events with no amount — sign-ups, installs, verifications. | Anything that decides money — purchases, deposits, refunds. | | Authentication | A public token that ships in your page source, plus the list of domains allowed to use it. Nothing secret is involved. | A secret token sent in the request header, optionally hardened with request signing (HMAC) and a list of allowed IP addresses. | | Can the amount be altered? | Yes. The amount is whatever the browser sends, so a visitor can edit it before it reaches us. | No, once request signing is on — the signature covers the whole body, amount included. | | Duplicate protection | Optional. dedupKey is your own id for the conversion; leave it out and the pixel script falls back to a key of its own for that page and browser session — a reload or Back counts once, but the same purchase made again in a new session counts twice. A direct request without dedupKey has no protection at all. | Built in. dedupKey is required, so retries and replays collapse into one conversion. | | Refunds and chargebacks | Not supported. A refunded order cannot be reversed from the browser. | Send the same conversion with reversal set to true and the credit is taken back. | | Delivery | Send and hope. Ad blockers, a tab closed too early or a failed request lose events silently, with no retry. | Your server owns delivery and can retry safely: dedupKey means a repeated send counts once, never twice. | | Do you find out about failures? | No. The endpoint returns the same empty success response (HTTP 204) to every well-formed request by design, so a dropped event looks exactly like an accepted one. Only a malformed request or an unsupported content type is refused (HTTP 400 or 415). | Partly. It answers HTTP 202 once the request is received, and an explicit error when the request itself is invalid or unauthenticated. Whether the conversion is credited is decided afterwards and is not reported back. | | Setup effort | Around 5 minutes — one snippet, pasted site-wide. | Around 15 minutes — store the ref id on landing, then POST from your backend. | Both routes run through the same validation, duplicate checks, fraud checks and minimum-value rules — a browser-reported amount is not discounted or scored more harshly. That is precisely why an amount you cannot vouch for should never be reported from the browser in the first place. ### Attribution strictness for wallets Web3 conversions are detected on the blockchain, from the visitor's crypto wallet. For a creator to be credited, that wallet has to be linked to the referral the visitor arrived with — a wallet binding. Attribution strictness decides how much a binding counts when the visitor signed it with their own wallet to prove they own it. New accounts require signed bindings by default; accounts created before that default prefer signed bindings but still count unsigned ones. Strictness is a per-company setting — you change it on the Configuration tab under On-chain attribution. ### Test mode Events sent with the test flag are practice runs: they get the same reception checks as a real conversion — shape, token, ref id and duplicates — but are never matched to a campaign, never billed and never credit a creator. An accepted test therefore proves your integration reaches us, not that a campaign's amount tiers, plan ids or new-users rule will accept the live event. Your account has a dedicated test ref id, and the Configuration tab can fire a test event for you and confirm automatically when it lands. The test ref id only works together with the test flag — real conversions must carry the ref id captured from the visitor's click. ### Event catalog Every event type a campaign action can pay for — the list is closed. Each set-up guide describes the fields an event needs and shows its exact request. #### Web2 events — you report these You report these from your website (pixel) or backend (postback). The guide for your transport lists each event's fields and its exact request. | Event | Description | | --- | --- | | Sign-up (`SIGN_UP`) | A new user registers on your product. | | KYC verification (`KYC_VERIFICATION`) | A user completes identity verification (KYC). | | App install (`APP_INSTALL`) | A user installs your mobile app. | | Purchase (`PURCHASE`) | A user makes a purchase. Send the order amount as orderValue. (Value-gated) (Postback recommended) | | Deposit (`DEPOSIT`) | A user funds their account. Send the deposit amount as depositValue. (Value-gated) (Postback recommended) | | Subscription (`SUBSCRIPTION`) | A user starts a paid subscription. Send the plan they subscribed to as qualifyingPlan — the payout is decided by matching it against the action's plan tiers, so no amount is sent. (Plan required) | #### Web3 events — detected on-chain Detected on-chain by watching the contract configured on the action — you never send these. A postback carrying one of these types is rejected outright; a pixel carrying one is dropped during attribution. Either way it is never credited. | Event | Description | | --- | --- | | Wallet activity (`WALLET_ACTIVITY`) | A wallet interacts with your configured contract. | | Token acquisition (`TOKEN_ACQUISITION`) | A wallet acquires at least the configured minimum amount of your token. | | Deposit / stake (`DEPOSIT_STAKE`) | A wallet deposits or stakes at least the configured minimum into your contract. | | NFT mint (`NFT_MINT`) | A wallet mints from your configured NFT collection. | Detected is not the same as credited: the chain does not say who referred the wallet. You have to bind the connected wallet to the referral with the pixel's identify call, or these conversions are never attributed to a creator. See section 5 (Set up web3 tracking). ### How a conversion is credited A request that reaches us is received, not credited. Whether it pays out is decided afterwards, against the campaign's configuration, by these rules. - **The event catalog is closed** — The event types on this page are the complete list — there are no custom event types. A postback whose content.type is not a web2 event type is refused with HTTP 500 and the problem type tag:validation_error; the pixel accepts it and drops it. To pay for a milestone of your own, report it as the event whose pricing fits: a flat fee as any of `SIGN_UP`, `KYC_VERIFICATION`, `APP_INSTALL`, a fee per named level as `SUBSCRIPTION` with the level as qualifyingPlan, or a fee graded by an amount as `PURCHASE`, `DEPOSIT`. Creators see that event's name, so describe the real milestone in the action's description. - **One action per event type** — A campaign carries at most one action for each event type, and a conversion is matched to that action by its type alone. One campaign therefore cannot pay two prices for the same event type — use a different event type or a second campaign. A conversion whose type the campaign has no action for is not credited. - **How each event is priced** — Flat (`SIGN_UP`, `KYC_VERIFICATION`, `APP_INSTALL`, `WALLET_ACTIVITY`, `NFT_MINT`): every credited conversion earns the same amount. Amount tiers (`PURCHASE`, `DEPOSIT`, `TOKEN_ACQUISITION`, `DEPOSIT_STAKE`): the conversion earns the tier with the highest minimum its amount meets; below the lowest minimum it is not credited. Plans (`SUBSCRIPTION`): qualifyingPlan must equal one of the action's plan ids exactly — it is case-sensitive and there is no default plan — or it is not credited. - **dedupKey pays once per campaign and event type** — A dedupKey is credited at most once per campaign and event type: an event whose key was already accepted for the same event type in the same campaign is rejected as a duplicate, whichever creator's link it came through. The same key on another event type, or in another campaign, counts on its own. A reversal (refund or chargeback) frees the key, so the user's next real conversion with it is credited again. Retries with the same key stay safe; give a repeat purchase or a re-subscription a new key. Leave dedupKey out and the pixel script (influence360.js) sends a key of its own for that page and browser session, so a reload or the Back button does not count twice — a direct request without one has no duplicate protection at all, and your own order or user id (e.g. deposit:order-1001) is always the better key. The postback requires one. - **New-users-only actions compare dedupKey across your company** — On an action marked new users only, dedupKey is also the user's identity: the conversion is refused when a conversion with the same key was already credited for your company, in any campaign. For such an action, build the key from the user rather than the order (e.g. deposit:user-42) — the event-type prefix keeps a user's earlier sign-up from counting against their first deposit. Without a dedupKey, every conversion counts as a new user. - **Other checks on a live conversion** — A live conversion is also refused when its ref id matches no creator's link, when a postback's token belongs to a different company than the campaign, when the campaign is not live (funded, or pending funding), when the creator's participation is not approved, when the latest click on the creator's link before the conversion — by any visitor, not necessarily the one converting — is older than the action's attribution window, when the conversion is the referring creator's own (the converting wallet, or a dedupKey containing it, is their payout wallet), or when fraud scoring flags a burst: more than 20 events on one ref id, or more than 50 from one IP address, that happened within 60 seconds — judged by when each event happened, not when it reached us. The per-IP limit does not apply to the postback, whose IP is your own server. - **Received is not credited** — The postback answers HTTP 202 once the request is received and its fields are valid; an invalid or unauthenticated request gets an explicit error instead. Everything else on this list is decided afterwards, and that outcome is not sent back to you. The pixel answers 204 to any well-formed request, whether or not the event is then kept. - **Test events check reception, not the campaign** — A test event (test: true) is kept out of billing entirely, so it is never matched to a campaign. It checks that the request is well-formed, carries a ref id, is not a duplicate and is not part of a burst — but not the campaign's action for that type, its amount tiers or plan ids, or new-users-only. An accepted test proves your integration reaches us; it does not prove the same live event will be credited. #### Why a test event is rejected The test events panel shows one of these codes when a test event is rejected. | Code | Meaning | | --- | --- | | `MISSING_FIELDS` | content.type was missing or is not a web2 event type. The pixel accepts such a request; this is where it is dropped. | | `UNATTRIBUTED` | An on-chain event carried no referral tag, and its wallet had no referral binding in the 30 days before the activity. A web2 request without a ref id never gets this far: the pixel drops it and the postback refuses it. | | `DUPLICATE` | A test event with the same dedupKey and event type was already accepted for your company. Use a fresh, unique dedupKey (e.g. a UUID) for each test. | | `FUTURE_TIMESTAMP` | The recorded event time was more than 5 minutes ahead of our clock. The time is stamped on our side, not taken from your request, so send it again. | | `FRAUD_HIGH_RISK` | Fraud scoring flagged a burst: more than 20 events on one ref id, or more than 50 from one IP address, that happened within 60 seconds. Every test event shares your test ref id, so space tests out. The per-IP limit does not apply to the postback. | | `INVALID_CONTENT` | content did not fit its event type — most often an amount that is not a plain decimal number, such as "1,299.00", "$49.99" or "49,99". Send amounts as digits with an optional . and fraction ("1299.00"): no sign, thousands separator or currency. | | `INVALID_FIELDS` | A field is longer than we accept: refId over 64 characters, dedupKey over 256 or the wallet address over 128. Shorten it — we never cut a value, since a shortened dedupKey could merge two users. | ## 3. Set up the pixel Source: https://influence360.io/integrations/pixel · ~5 min setup Report conversions straight from your website. Three steps: pick a browser key, paste one snippet site-wide, then verify with a test event. ### Step 1 — Get a public token The pixel is keyed by a public token — a browser key identifying your company. It is visible in your page source by design (not a secret) and re-viewable any time. ### Step 2 — Install the snippet Two ways to do this. **influence360.js** is the short path; **Your own code** posts the same request by hand, for teams that will not put a third-party script on their pages. Steps 1 and 3 are the same either way. #### influence360.js — Install the snippet One snippet, pasted site-wide — it loads influence360.js, captures the ref id from the creator's link and reports conversions. - Paste the snippet below on every page (e.g. in your site layout or ``). It loads influence360.js and runs `influence360('init', …)` to capture the ref id. - On each conversion success page, call `influence360('track', '', …)` with the matching event type — pass your `dedupKey`, plus the event's own field where it has one (`orderValue` for a purchase, `depositValue` for a deposit, `qualifyingPlan` for a subscription). - To send events through your own first-party proxy or a regional collector, change the `collectUrl` passed to `init`. ```html ``` #### Your own code — Call the endpoint directly The endpoint is public, so you can post to it yourself. Same URL, same body, same result: nothing downstream can tell the difference. You take on the two jobs influence360.js was doing for you, and both fail quietly if you get them wrong. **1. Capture the ref id yourself** Our redirect adds the ref id to your destination URL once, on the landing page. Nothing else will put it back, so read it there and store it before the visitor navigates away. ```js // Run on EVERY page. Our redirect adds ?influence360RefId=… to your destination URL exactly // once — on the landing page — so if you miss it there, the referral is gone. (function () { var ref = new URLSearchParams(window.location.search).get('influence360RefId'); if (!ref) return; // Same cookie name and 90-day lifetime the pixel uses, so you can switch to influence360.js // later without losing referrals already captured. document.cookie = '_influence360_ref_id=' + encodeURIComponent(ref) + '; Max-Age=7776000; Path=/; SameSite=Lax; Secure'; })(); // Read it back when the visitor converts. function influence360RefId() { var match = document.cookie.match(/(?:^|;\s*)_influence360_ref_id=([^;]*)/); return match ? decodeURIComponent(match[1]) : null; } ``` **2. Post the conversion** One request per conversion, to the same endpoint the pixel uses. The public token goes on the query string, not in the body. ```js function reportConversion(content, dedupKey) { var url = 'https://tracking.influence360.io/public/pixel/conversion?publicToken='; var body = JSON.stringify({ refId: influence360RefId(), // null when the visitor arrived without a referral — safe to send dedupKey: dedupKey, eventSourceUrl: window.location.href, clientEventAtMs: Date.now(), content: content, }); // The content type is load-bearing: 'text/plain;charset=UTF-8' is CORS-safelisted, so this // cross-origin POST skips the preflight. Sending application/json triggers a preflight that the // collector does not answer, and the event is lost with no error you can see. 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, // survives the page unload a conversion redirect usually causes mode: 'no-cors', // the reply is an empty 204; there is nothing to read }).catch(function () {}); } // On your conversion success page: reportConversion({ "type": "SIGN_UP" }, 'sign_up:'); ``` **Send it as text/plain, not application/json** This is the mistake that costs people an afternoon. Browsers let a cross-origin POST through without a preflight only for a few content types, and application/json is not one of them — the collector does not answer preflights, so the request never arrives. Nothing throws, the console stays clean, and your conversions simply never show up. Use text/plain;charset=UTF-8 and the browser sends it straight through. **Check it from a terminal first** The same request as curl, so you can confirm the token and body before touching your site. Set Origin to a domain the token allows — a request from an unlisted domain is discarded, and you still get the same empty success back. ```bash curl -X POST 'https://tracking.influence360.io/public/pixel/conversion?publicToken=' \ -H 'Content-Type: text/plain;charset=UTF-8' \ -H 'Origin: https://your-site.example' \ -d '{ "refId": "", "dedupKey": "sign_up:", "content": { "type": "SIGN_UP" } }' ``` **What you give up** The pixel also handles cross-subdomain cookies, retry on page unload and the wallet binding used for web3 tracking. Posting directly gets you conversions only. Reversals and refunds are not available on this endpoint at all — from either the pixel or your own code — so anything carrying an amount still belongs on the server postback. **If the event you are reporting carries an amount (Purchase, Deposit), send it from your backend instead — this applies to both options above.** The amount you pass here is whatever the browser reports, and a visitor can edit it. The pixel also cannot reverse the conversion when the order is refunded, and without your own dedupKey only a repeat within the same browser session is caught. Report Purchase and Deposit through the server postback instead. #### Web2 events — you report these The page describes the event picked in step 2; every event is listed here, with its call for both options. ##### Sign-up (`SIGN_UP`) A new user registers on your product. No event-specific fields — content carries only its type influence360.js: ```js influence360('track', 'SIGN_UP', { dedupKey: 'sign_up:' }); ``` Your own code: ```js navigator.sendBeacon( 'https://tracking.influence360.io/public/pixel/conversion?publicToken=', // text/plain is required from a browser — application/json is dropped at the preflight. new Blob([JSON.stringify({ refId: influence360RefId(), dedupKey: 'sign_up:', content: { "type": "SIGN_UP" }, })], { type: 'text/plain;charset=UTF-8' }), ); ``` ##### KYC verification (`KYC_VERIFICATION`) A user completes identity verification (KYC). No event-specific fields — content carries only its type influence360.js: ```js influence360('track', 'KYC_VERIFICATION', { dedupKey: 'kyc_verification:' }); ``` Your own code: ```js navigator.sendBeacon( 'https://tracking.influence360.io/public/pixel/conversion?publicToken=', // text/plain is required from a browser — application/json is dropped at the preflight. new Blob([JSON.stringify({ refId: influence360RefId(), dedupKey: 'kyc_verification:', content: { "type": "KYC_VERIFICATION" }, })], { type: 'text/plain;charset=UTF-8' }), ); ``` ##### App install (`APP_INSTALL`) A user installs your mobile app. No event-specific fields — content carries only its type influence360.js: ```js influence360('track', 'APP_INSTALL', { dedupKey: 'app_install:' }); ``` Your own code: ```js navigator.sendBeacon( 'https://tracking.influence360.io/public/pixel/conversion?publicToken=', // text/plain is required from a browser — application/json is dropped at the preflight. new Blob([JSON.stringify({ refId: influence360RefId(), dedupKey: 'app_install:', content: { "type": "APP_INSTALL" }, })], { type: 'text/plain;charset=UTF-8' }), ); ``` ##### Purchase (`PURCHASE`) _Value-gated · Postback recommended_ A user makes a purchase. Send the order amount as orderValue. Event-specific fields: | Field | Requirement | Description | | --- | --- | --- | | `orderValue` | Required | The conversion amount, as a JSON string holding a plain decimal number (e.g. "49.99"): digits with an optional . and fraction — no sign, thousands separator, currency symbol or decimal comma; anything else is rejected. Must meet the action's configured minimum, or the conversion is rejected. | influence360.js: ```js influence360('track', 'PURCHASE', { dedupKey: 'purchase:', orderValue: '49.99' }); ``` Your own code: ```js navigator.sendBeacon( 'https://tracking.influence360.io/public/pixel/conversion?publicToken=', // text/plain is required from a browser — application/json is dropped at the preflight. new Blob([JSON.stringify({ refId: influence360RefId(), dedupKey: 'purchase:', content: { "type": "PURCHASE", "orderValue": "49.99" }, })], { type: 'text/plain;charset=UTF-8' }), ); ``` ##### Deposit (`DEPOSIT`) _Value-gated · Postback recommended_ A user funds their account. Send the deposit amount as depositValue. Event-specific fields: | Field | Requirement | Description | | --- | --- | --- | | `depositValue` | Required | The conversion amount, as a JSON string holding a plain decimal number (e.g. "49.99"): digits with an optional . and fraction — no sign, thousands separator, currency symbol or decimal comma; anything else is rejected. Must meet the action's configured minimum, or the conversion is rejected. | influence360.js: ```js influence360('track', 'DEPOSIT', { dedupKey: 'deposit:', depositValue: '49.99' }); ``` Your own code: ```js navigator.sendBeacon( 'https://tracking.influence360.io/public/pixel/conversion?publicToken=', // text/plain is required from a browser — application/json is dropped at the preflight. new Blob([JSON.stringify({ refId: influence360RefId(), dedupKey: 'deposit:', content: { "type": "DEPOSIT", "depositValue": "49.99" }, })], { type: 'text/plain;charset=UTF-8' }), ); ``` ##### Subscription (`SUBSCRIPTION`) _Plan required_ A user starts a paid subscription. Send the plan they subscribed to as qualifyingPlan — the payout is decided by matching it against the action's plan tiers, so no amount is sent. Event-specific fields: | Field | Requirement | Description | | --- | --- | --- | | `qualifyingPlan` | Required | The identifier of the plan the user subscribed to (e.g. "pro-monthly"). | influence360.js: ```js influence360('track', 'SUBSCRIPTION', { dedupKey: 'subscription:', qualifyingPlan: 'pro-monthly' }); ``` Your own code: ```js navigator.sendBeacon( 'https://tracking.influence360.io/public/pixel/conversion?publicToken=', // text/plain is required from a browser — application/json is dropped at the preflight. new Blob([JSON.stringify({ refId: influence360RefId(), dedupKey: 'subscription:', content: { "type": "SUBSCRIPTION", "qualifyingPlan": "pro-monthly" }, })], { type: 'text/plain;charset=UTF-8' }), ); ``` #### Request fields The common fields sit at the top level of the request; the event-specific ones go inside the `content` object, selected by its `type`. There is no top-level event field. | Field | Requirement | Description | | --- | --- | --- | | `refId` | Required | The ref id captured from the creator's link — the campaign and creator are resolved from it. | | `content` | Required | The event-specific object. Its type field selects the event and gates which other fields it carries — see the event details above. | | `publicToken` | Required | Your public token (browser key), sent as the ?publicToken= query parameter on the request URL — not in the JSON body. Identifies your company and is checked against the token's allowed domains. | | `dedupKey` | Recommended | Your id for this conversion — an order or user id, prefixed with the event type (e.g. purchase:1001). It is credited once per campaign and event type, and a refund frees it. Required on the postback — it is what makes retries safe; strongly recommended on the pixel, whose script otherwise uses a key of its own for the browser session. | | `test` | Optional | Marks a verification canary: checked on receipt but never matched to a campaign or billed. See “How a conversion is credited” for what it does not check. | | `clientEventAtMs` | Optional | Optional epoch-millis timestamp of when your side observed the event. Diagnostic only — we record the authoritative time on receipt, so it never affects billing or attribution; it just helps you spot delivery lag on queued or retried events. | | `eventSourceUrl` | Optional | The page URL the event fired on. The pixel script captures it automatically — you only set it yourself when posting to the pixel endpoint directly. Diagnostic only; never used for billing or attribution. | Money values inside content are strings, e.g. "49.99" — the same in the pixel's JS API and in the postback body. ### Step 3 — Verify with a test event Fire a verification canary (never billed) from the Configuration tab — we confirm your integration automatically once it lands. ### Transparency & verification influence360.js is the script that reports conversions from your site. This page covers what it does, what it stores, what it never does, and how to verify it. #### What it stores in the browser Three writes, all first-party on your own domain. This list is complete — if you find a fourth in your browser's Application tab, that is a bug and we want the report. | Name | integrations.docs.transparency.storage.cols.kind | Lifetime | Scope | Purpose | | --- | --- | --- | --- | --- | | `_influence360_ref_id` | Cookie | 90 days | Your domain, all paths | The referral id the visitor arrived with, so a conversion can be credited to the creator who sent them. | | `_influence360_ref_id` | localStorage | until cleared | Your origin | A copy of the same referral id, so attribution survives the cookie being evicted. Not a second identifier — same key, same value. | | `__influence360_d` | Cookie | transient | Candidate parent domain | Written and deleted within the same call, to find the widest domain that accepts a cookie so a click on one subdomain converts on another. Never persists. | The referral cookie is written with Path=/; SameSite=Lax; Secure — first-party, sent only over HTTPS, and not sent on cross-site requests. Nothing is written to a domain other than yours, and nothing is read that the script did not write. If you set the cookieDomain option, the cookie is scoped to exactly that domain instead of the auto-detected one. #### What it does not do Each of these is a property of the bundle you can confirm yourself: search the source for the API it would have to call, and it is not there. - **No device fingerprinting** — No canvas, font, WebGL or audio probing, no screen or hardware enumeration, no attempt to identify a device across sites. - **Reads only what it wrote** — It reads its own referral cookie and its own localStorage key. It does not enumerate your cookies, storage or session data. - **No page content** — No form fields, no keystrokes, no clipboard, no DOM scraping, no session recording, no screenshots. - **No third-party calls** — The only request it makes is the conversion beacon to the collector shown above. No ad networks, no analytics vendors, no tag loaders, no dynamically injected scripts. - **No cross-site tracking** — Storage is first-party on your domain only. There is no third-party cookie, no iframe, no cross-site identifier, and no advertising id. - **No personal data we choose** — The only field that could carry personal data is the dedupKey your own code supplies. Send an order id or a hash, prefixed with the event type (e.g. purchase:1001) — the pixel neither reads nor infers an email, a name or an address on its own. #### What happens when it fails The honest answer to “will this break my checkout?”, with the mechanism behind each guarantee named so you can check it rather than take it. - **It never throws into your page** — Every entry point is wrapped, including storage access and the network call. A failure inside the tracker cannot become an exception in your own code. - **It never blocks rendering** — The snippet loads the script asynchronously, and conversions are sent fire-and-forget through sendBeacon (or a keepalive fetch). Nothing waits on a response, and there is no response to wait for. - **Blocked storage is not an error** — In private mode, with storage disabled, or under a sandbox, reads and writes fail silently. Attribution is lost for that visitor; the page is unaffected. - **A tampered build fails closed** — If you pin a version with an integrity hash, a browser that receives different bytes refuses to execute them. The worst case is no tracking, never wrong tracking or unexpected code. #### Verify the bytes you load Pinned builds are immutable and never republished with different bytes, so a hash that matched yesterday still matches today. The manifest is the authoritative record of what we have published. Manifest: https://tracking-pixel.influence360.io/manifest.json - **1. Hash what the CDN served you** — Needs nothing but curl. Proves the file your visitors receive is the file listed in the manifest — the same sha384 a pinned snippet carries. - **2. Check the build provenance** — Every published build carries a sigstore attestation binding its digest to the workflow, repository and commit that produced it. Proves the bytes came out of our public build pipeline and not from someone with CDN access. - **3. Rebuild it yourself** — The build is reproducible: rebuilding the commit shown in the Commit column above produces byte-identical output. The command prints a sha256 — compare it with the one in the table. This is the check that requires trusting nobody: not us, and not GitHub. **What the provenance attestation proves** — Builds from this repository are signed by public sigstore and recorded in its public Rekor transparency log, so the attestation is independently auditable: you can confirm the bytes came out of this repository's workflow without taking our word for it, or GitHub's. Verified on the current build — the signing certificate is issued by sigstore.dev, and the Rekor entry is retrievable anonymously with a signed inclusion proof. Builds published before this repository became public on 5 August 2026 were signed by GitHub's own sigstore instance instead and carry no public log entry. Step 3 goes further still, tying those bytes to source you can read. #### Staying informed, and reporting a problem A pinned version never changes under you. If you use the auto-update channel, or your script-monitoring tool alerts on a change, these are how you correlate and how you reach us. | Resource | URL | | --- | --- | | Published-builds feed (Atom) | https://influence360.io/pixel-builds.xml | | Report a vulnerability | https://github.com/Influence360/tracking-pixel/security/advisories/new | | View the source | https://github.com/Influence360/tracking-pixel | | Apache-2.0 licence | https://github.com/Influence360/tracking-pixel/blob/main/LICENSE | ## 4. Set up the postback Source: https://influence360.io/integrations/postback · ~15 min setup · Recommended for money events Report conversions from your backend — authenticated, idempotent and reversible. Works with or without the browser pixel. **Use this for anything that decides money** — Purchase and Deposit amounts set what a creator gets paid. Reported from your backend, the request is authenticated, the amount can be signed so nobody can alter it, dedupKey makes retries safe, and a refund can be reversed. None of that is possible from the browser pixel. ### Step 1 — Create an API token Postbacks authenticate with a private API token. The secret is shown once, when you create or rotate the token — store it on your server and never expose it in the browser. ### Step 2 — Capture the ref id When a visitor arrives through a creator's link, our redirect appends `?influence360RefId=…` to your destination URL. Store that value against the visitor (session, your own cookie or the user record) — you will send it back as `refId` with each conversion. Also running the browser pixel? It already persists the same value in the `_influence360_ref_id` first-party cookie, so your server can read it from there instead. ### Step 3 — Send the postback POST a JSON body with the ref id and the conversion details — the campaign is resolved from the ref id. - When the user converts, POST a JSON body with the ref id and a `content` object holding the conversion (its `type` plus that type's own fields). The campaign is resolved from the ref id. - Authenticate the request with the `Authorization: Bearer ` header. ```bash curl -X POST 'https://tracking.influence360.io/public/postback/conversion' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "refId": "", "dedupKey": "sign_up:", "content": { "type": "SIGN_UP" } }' ``` #### Web2 events — you report these The page describes the event picked in step 3; every event is listed here, with its request. ##### Sign-up (`SIGN_UP`) A new user registers on your product. No event-specific fields — content carries only its type ```bash curl -X POST 'https://tracking.influence360.io/public/postback/conversion' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "refId": "", "dedupKey": "sign_up:", "content": { "type": "SIGN_UP" } }' ``` ##### KYC verification (`KYC_VERIFICATION`) A user completes identity verification (KYC). No event-specific fields — content carries only its type ```bash curl -X POST 'https://tracking.influence360.io/public/postback/conversion' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "refId": "", "dedupKey": "kyc_verification:", "content": { "type": "KYC_VERIFICATION" } }' ``` ##### App install (`APP_INSTALL`) A user installs your mobile app. No event-specific fields — content carries only its type ```bash curl -X POST 'https://tracking.influence360.io/public/postback/conversion' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "refId": "", "dedupKey": "app_install:", "content": { "type": "APP_INSTALL" } }' ``` ##### Purchase (`PURCHASE`) _Value-gated · Postback recommended_ A user makes a purchase. Send the order amount as orderValue. Event-specific fields: | Field | Requirement | Description | | --- | --- | --- | | `orderValue` | Required | The conversion amount, as a JSON string holding a plain decimal number (e.g. "49.99"): digits with an optional . and fraction — no sign, thousands separator, currency symbol or decimal comma; anything else is rejected. Must meet the action's configured minimum, or the conversion is rejected. | ```bash curl -X POST 'https://tracking.influence360.io/public/postback/conversion' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "refId": "", "dedupKey": "purchase:", "content": { "type": "PURCHASE", "orderValue": "49.99" } }' ``` ##### Deposit (`DEPOSIT`) _Value-gated · Postback recommended_ A user funds their account. Send the deposit amount as depositValue. Event-specific fields: | Field | Requirement | Description | | --- | --- | --- | | `depositValue` | Required | The conversion amount, as a JSON string holding a plain decimal number (e.g. "49.99"): digits with an optional . and fraction — no sign, thousands separator, currency symbol or decimal comma; anything else is rejected. Must meet the action's configured minimum, or the conversion is rejected. | ```bash curl -X POST 'https://tracking.influence360.io/public/postback/conversion' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "refId": "", "dedupKey": "deposit:", "content": { "type": "DEPOSIT", "depositValue": "49.99" } }' ``` ##### Subscription (`SUBSCRIPTION`) _Plan required_ A user starts a paid subscription. Send the plan they subscribed to as qualifyingPlan — the payout is decided by matching it against the action's plan tiers, so no amount is sent. Event-specific fields: | Field | Requirement | Description | | --- | --- | --- | | `qualifyingPlan` | Required | The identifier of the plan the user subscribed to (e.g. "pro-monthly"). | ```bash curl -X POST 'https://tracking.influence360.io/public/postback/conversion' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "refId": "", "dedupKey": "subscription:", "content": { "type": "SUBSCRIPTION", "qualifyingPlan": "pro-monthly" } }' ``` #### Request fields The common fields sit at the top level of the request; the event-specific ones go inside the `content` object, selected by its `type`. There is no top-level event field. | Field | Requirement | Description | | --- | --- | --- | | `Authorization` | Required | Authenticates your server. The secret is shown only once when you create or rotate a token — paste your stored secret in place of . Lost it? Rotate the token on the Tokens page. | | `refId` | Required | The ref id captured from the creator's link — the campaign and creator are resolved from it. | | `content` | Required | The event-specific object. Its type field selects the event and gates which other fields it carries — see the event details above. | | `dedupKey` | Required | Your id for this conversion — an order or user id, prefixed with the event type (e.g. purchase:1001). It is credited once per campaign and event type, and a refund frees it. Required on the postback — it is what makes retries safe; strongly recommended on the pixel, whose script otherwise uses a key of its own for the browser session. | | `test` | Optional | Marks a verification canary: checked on receipt but never matched to a campaign or billed. See “How a conversion is credited” for what it does not check. | | `reversal` | Optional | Set true to reverse a previously reported conversion (refund or chargeback). | | `clientEventAtMs` | Optional | Optional epoch-millis timestamp of when your side observed the event. Diagnostic only — we record the authoritative time on receipt, so it never affects billing or attribution; it just helps you spot delivery lag on queued or retried events. | Money values inside content are strings, e.g. "49.99" — the same in the pixel's JS API and in the postback body. ### Step 4 — Verify with a test event Fire a verification canary (never billed) from the Configuration tab — we confirm your integration automatically once it lands. ## 5. Set up web3 tracking Source: https://influence360.io/integrations/web3 · ~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. _Snippets in this section are shown for `ETHEREUM`; substitute the chain of the wallet the visitor connected._ ### Step 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. ### Step 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. ```html ``` ### Step 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. - Call `influence360('identify', address, …)` as soon as a wallet connects, and again whenever the connected account changes. - 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. - 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. ```js // Run once the visitor connects a wallet (and again on account switch). async function onWalletConnected(address) { influence360('identify', '', { chain: 'ETHEREUM' }); } ``` Bindings for TRON are accepted, but signature verification for TRON is not supported yet — they can only ever be unsigned. So Require signed does not filter TRON: its unsigned bindings still count, ranked as under Prefer signed. ### Step 4 — 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. ### Web3 events — detected on-chain Detected on-chain by watching the contract configured on the action — you never send these. A postback carrying one of these types is rejected outright; a pixel carrying one is dropped during attribution. Either way it is never credited. | Event | Description | | --- | --- | | Wallet activity (`WALLET_ACTIVITY`) | A wallet interacts with your configured contract. | | Token acquisition (`TOKEN_ACQUISITION`) | A wallet acquires at least the configured minimum amount of your token. | | Deposit / stake (`DEPOSIT_STAKE`) | A wallet deposits or stakes at least the configured minimum into your contract. | | NFT mint (`NFT_MINT`) | A wallet mints from your configured NFT collection. | ### 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: new accounts require signed bindings by default, so for them an unsigned binding does not count at all (except on chains that cannot sign yet); older accounts prefer signed bindings over unsigned ones for the same wallet, and still count an unsigned one when no signed one exists. 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. ```text influence360 wallet attribution ref: wallet: chain:ETHEREUM issuedAt:1700000000000 ``` #### Producing a 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. | Field | Requirement | Description | | --- | --- | --- | | `refId` | Required | The ref id the visitor arrived with, from the pixel's first-party cookie. The binding is tied to this referral. | | `wallet` | Required | The wallet address the visitor connected — the address we match against on-chain activity. | | `chain` | Optional | The chain of the connected wallet. Metadata for a plain binding; required when you send a signature, as it selects the verification scheme. | | `signature` | Optional | Hex wallet signature over the canonical binding message, proving control of the address. Absent or invalid means the binding is kept but unsigned. | | `signedAtMs` | Optional | Epoch milliseconds the signature was issued — the same value used as issuedAt in the signed message. Required whenever a signature is sent. | | `test` | Optional | Marks a canary binding — validated the same way, never used to credit a payout. | ```bash curl -X POST 'https://tracking.influence360.io/public/pixel/identify?publicToken=' \ -H 'Content-Type: application/json' \ -d '{ "refId": "", "wallet": "", "chain": "ETHEREUM" }' ``` **Binding from browser JavaScript** A 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. ```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='; 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. ## 6. Tokens & security Source: https://influence360.io/integrations/security Two kinds of token authenticate your traffic, and four controls keep that traffic yours: allowed domains, request signing, IP allow-lists and rotation. ### The two token kinds | | Public token | Private token | | --- | --- | --- | | Summary | Keys the browser pixel. Meant to be visible in your page source — not a secret. Re-viewable any time; disable or rotate without touching your other tokens. | Authenticates server postbacks. The secret is shown only once when created or rotated, so store it securely and never expose it in the browser. | | Visibility | Public — visible in page source | Secret — server-side only | | Used by | Browser pixel | Server postback | | Sent as | ?publicToken= query param | Authorization: Bearer … | | Secret shown | Not a secret · re-viewable | Once, at create / rotate | ### Security controls #### Allowed domains (Public token) Each public token can include a domain allow-list. Events sent from pages on unapproved domains are silently discarded. The pixel still receives an empty success response, preventing attackers from learning which domains are allowed through probing. An empty list allows all domains. Entries include subdomains, so example.com also covers shop.example.com. #### Request signing (HMAC) (Private token) A private token can include an HMAC secret. Your server sends X-Timestamp and X-Signature (a hex HMAC-SHA256 of the timestamp, newline, and raw body). We verify both and reject mismatches, so the bearer token alone is not enough. The X-Timestamp must be within a short tolerance of server time, which bounds replays; dedupKey then keeps an accepted conversion counted once. #### IP allow-list (Private token) A private token can be restricted to a set of IP addresses or CIDR ranges. Postbacks from any other address are rejected regardless of credentials. Combine with signing for defence in depth: the allow-list stops stray callers, the signature proves the payload came from you. #### Rotation (Both tokens) Rotate a token whenever a secret may have leaked, when someone with access leaves, or on your regular security schedule. Rotation invalidates the old value immediately. Keep two tokens per surface during planned rotations: add the new one, deploy, then revoke the old — zero downtime.