Analytics SDK
Specify measures conversions onchain, and that requires nothing on your side — no pixels, no tags, no SDK. That doesn’t change.
What onchain measurement can’t see is the middle of the funnel: the visit to your landing page, the signup, the moment a wallet connects on your site before the first transaction. The Analytics SDK fills in that gap. It’s a small, optional library you add to your own site that records funnel events — page views, wallet connections, and any product milestones you choose — so campaign reporting can show the full journey: saw ad → visited → converted.
What it is (and isn’t)
- It’s funnel visibility. Events captured by the SDK join against the onchain conversions Specify already observes, so you can see where the journey breaks down — did people visit and bounce, or connect a wallet and stall before the first transaction?
- It’s a retargeting audience. Visitors the SDK identifies — the interested-but-not-converted — can be reached again wherever they show up across the Specify network. See Retargeting your visitors.
- It’s never billed. Billing stays 100% onchain, always. There is no billable-event list and nothing you log can create a charge — events feed analytics and nothing else.
- It’s optional and additive. Campaigns, conversion attribution, and reporting all work without it. Skip it entirely and nothing degrades.
Quick start
npm install @specify-sh/advertiserimport { SpecifyAnalytics } from '@specify-sh/advertiser';
const analytics = new SpecifyAnalytics({ propertyKey: 'adv_…' });
// 1. Wire consent from your CMP / consent banner, on every page load:
onConsentChange((granted) =>
granted
? analytics.consentForEnhancedTracking()
: analytics.revokeEnhancedTrackingConsent(),
);
// 2. Tell Specify when a wallet connects on your site:
onWalletConnect((addresses) => analytics.identify(addresses));
// 3. Log the milestones you care about:
analytics.logEvent('signup_completed', { plan: 'pro' });You’ll receive your adv_ property key when we set up funnel analytics for your campaign — it identifies your site, the same way a publisher key identifies a publisher.
Page views and wallet connections are captured automatically (see below), so for many sites steps 1 and 2 are the whole integration.
Consent comes first — literally
Nothing leaves the page without consent. Until your consent banner reports approval and you call consentForEnhancedTracking(), events are held in memory only — never sent, never stored, gone when the tab closes. Granting consent releases the held events in order, so the landing-page view that happened moments before the user clicked “Accept” still makes it into the funnel. Revoking consent stops sending immediately.
Consent is deliberately not persisted by the SDK. It lives on the instance for the lifetime of the page, which keeps your CMP the single source of truth: call the gate on every page load where the user has consented, and a withdrawal takes effect on the very next pageview.
analytics.hasEnhancedTrackingConsent(); // -> booleanWith consent granted, requests are credentialed, which is what lets Specify recognise the visitor as the same person who saw the ad on a publisher site — the cross-site link that makes the funnel joinable. The SDK itself never sees or handles that identifier; the mechanism is the same server-side identity used by Enhanced Tracking on the publisher side.
What gets captured
Automatically:
page_view— on load and on client-side route changes in single-page apps. No configuration; duplicate views of the same URL are not re-emitted.wallet_detected/wallet_changed— the SDK passively notices wallets already connected to your page via EIP-6963 (with awindow.ethereumfallback). Detection is silent: it only ever reads already-connected accounts, never prompts, never opens a wallet, never requests permissions. Turn it off withprivacy: { disableWalletDetection: true }.
When you call it:
identify(addresses)— the strongest signal you can send. Call it in your wallet-connect callback (WalletConnect sessions are invisible to passive detection, so pass those here). Addresses accumulate and ride along on every subsequent event.logEvent(name, props?)— any product milestone:signup_completed,deposit_started,docs_viewed. Event names are lowercasea–z,0–9, and underscores, up to 64 characters;propsis any JSON-serialisable object up to 8 KB.
Every event carries the page URL and referrer, a per-tab session id, the wallets known so far, and the click id (spclid) when the visit came from a Specify ad — captured from the landing URL automatically and kept for the whole session, so events after the first navigation still tie back to the ad.
Events are batched (flushed every few seconds, on batch fill, and when the page is hidden or closed), so the SDK adds no meaningful network overhead.
Retargeting your visitors
The most valuable audience in advertising is the one that already came to you: people who saw your ad, visited your site, maybe connected a wallet — and left without converting. The Analytics SDK turns that audience into one your campaigns can reach again.
When a visitor connects a wallet on your site — passed through identify() or picked up by passive detection — that wallet becomes part of what Specify knows about your funnel. Because Specify serves ads by wallet across the whole network, those visitors can be retargeted wherever they show up next: on the wallets, explorers, and dapps where Specify serves, your campaign can specifically reach the users who visited but didn’t convert. For consenting users, Enhanced Tracking extends that reach further — they can be recognised even on network pages where no wallet is connected at all.
The same signal works in reverse, and this is where it quietly pays for itself: combined with exclusion targeting, visitors who did convert stop being shown your acquisition ads — so your budget concentrates on the warm middle of the funnel instead of people who are already users.
Talk to us about enabling a retargeting segment for your campaign — because it’s built from your own visitors, it’s typically the highest-intent audience a campaign can run against.
Installing through a tag manager
If you’d rather not touch your bundle, the same feature set is available as a script through Google Tag Manager or any CMS that lets you add HTML:
<script>window.specifyAnalytics=window.specifyAnalytics||function(){(window.specifyAnalytics.q=window.specifyAnalytics.q||[]).push(arguments)};</script>
<script async src="https://spfsrv.com/sdk/advertiser/v1.js"></script>Then use commands anywhere on the page — before or after the script loads; calls are queued and replayed in order:
<script>
specifyAnalytics('init', { propertyKey: 'adv_…' });
specifyAnalytics('consent'); // from your CMP tag
specifyAnalytics('identify', ['0xabc…']); // on wallet connect
specifyAnalytics('event', 'signup_completed', { plan: 'pro' });
</script>Commands: init, consent, revokeConsent, identify, event. Tag ordering doesn’t matter — commands fired before init are buffered and run once it arrives.
API reference
| Method | Description |
|---|---|
new SpecifyAnalytics({ propertyKey, privacy?, edge? }) | Create a client. Throws if the property key is malformed. |
consentForEnhancedTracking() | Start sending events (credentialed). Call on every page load where consent is granted. |
revokeEnhancedTrackingConsent() | Stop sending immediately; later events are held in memory only. |
hasEnhancedTrackingConsent() | Whether events are currently being sent. |
identify(addressOrAddresses) | Register wallet address(es) connected on your site. Accumulates; lowercased; validates format. |
logEvent(name, props?) | Record a funnel milestone. Name: [a-z0-9_]{1,64}; props ≤ 8 KB JSON. |
getDetectedWallets() | Addresses found by passive detection, lowercased. |
destroy() | Flush pending events and detach all listeners — call when tearing down an SPA view. |
Privacy posture
- Consent-gated at the source. No consent means no network requests — not anonymised events, not “essential” events. None.
- No new data about anyone. The SDK sees only what your page already sees: its own URL, and wallets the user has already, voluntarily connected.
- Wallet detection never prompts and can be disabled entirely;
identify()andlogEvent()still work with detection off. - Nothing here affects billing. Events cannot trigger charges, and there’s no incentive mechanism tied to volume.
If you enforce a Content-Security-Policy, add https://spfsrv.com to connect-src (and to script-src if you use the tag-manager install).