Migrating from SDK v0.4.x
Specify SDK v1.0.0 introduces Enhanced Tracking — the ability to serve targeted ads to visitors who don’t have a wallet connected on your page. This guide covers what it means for existing integrations and how to upgrade.
The short version
- Nothing you have to do today. v0.4.2 integrations keep working, with the same request and response shapes. There is no deprecation deadline, and the v0.4.x reference stays available.
- Everything in v1.0.0 is additive. No API was removed, so the upgrade itself is a version bump — every v0.4.x call still compiles and behaves the same way.
- Upgrading takes about an hour and unlocks serving on pages and sessions where v0.4.x returns nothing.
Why upgrade
Today your placements only earn when a visitor has a connected wallet. On v1.0.0, any visitor Specify recognises — because they’ve connected a wallet before, on your site or anywhere on the network — can be served a targeted ad, on any page, wallet connected or not. In practice that means:
- Logged-out and pre-connect states fill. The visit before the user clicks “Connect” is no longer dead inventory.
- Wallet-free surfaces fill. Blogs, docs, dashboards, explorers — anywhere you can render a banner.
- Recognition survives disconnects, cleared cookies and new devices, because it heals through the wallet rather than depending on one fragile piece of client-side state.
Targeting quality, moderation, the one-ad-max rule and the no-filler rule are all unchanged. So is how you get paid.
A note on cacheMostRecentAddress
cacheMostRecentAddress, and the client-side cache behind it, are deprecated. The option is still accepted on v1.0.0 and still behaves as it always has, so nothing breaks if you leave it in place.
Enhanced Tracking supersedes it. The old cache banked one wallet in the visitor’s own browser storage, which meant it disappeared the moment they cleared site data and never travelled beyond your site. Recognition on v1.0.0 is resolved server-side, survives cache clears, and works across the whole network — the same use case, handled properly.
Migration guide (developers)
Step 0 — What’s actually changing
| v0.4.x | v1.0.0 | |
|---|---|---|
| Serving endpoint | app.specify.sh/api/ads | spfsrv.com/api/ads, credentialed once consent is granted |
| Serve with no wallet | Only via cacheMostRecentAddress (browser cache) | First-class, via Enhanced Tracking cookie (consent-gated) |
| Consent handling | n/a | consentForEnhancedTracking(), revokeEnhancedTrackingConsent(), hasEnhancedTrackingConsent() — new |
| Wallet identification outside a serve | n/a | identify(addresses) — new |
| Wallet detection | n/a | Automatic, passive, on by default; getDetectedWallets() and an opt-out flag |
Constructor: cacheMostRecentAddress | Supported | Deprecated — still accepted, superseded by Enhanced Tracking |
serve() signature | serve(addresses, { imageFormat, adUnitId }) | Unchanged, plus a new browser-friendly serve({ imageFormat, adUnitId }) form |
| Teardown | n/a | destroy() — new |
SpecifyAd shape, ImageFormats, error classes | — | Unchanged |
| Click redirect host | app.specify.sh/r/… | spfsrv.com/r/… (old links 302; ctaUrl is always correct as returned) |
Step 1 — Install
npm install @specify-sh/sdk@latestPin the major if you prefer: npm install @specify-sh/sdk@1.
Not installing from npm? The same SDK is available as a tag-managed script — see Google Tag Manager.
Step 2 — Drop cacheMostRecentAddress
const specify = new Specify({
publisherKey: process.env.NEXT_PUBLIC_SPECIFY_PUBLISHER_KEY,
- cacheMostRecentAddress: true,
});This one is a recommendation, not a requirement — the option is still accepted and your code works either way. Its use case, serving after a disconnect, is covered by Enhanced Tracking without you managing any client-side state, so there’s no reason to keep the extra storage around.
Step 3 — Wire the consent gate
Enhanced Tracking uses one cookie, set server-side on Specify’s own domain, and the SDK will not send or set it until you signal consent. Hook this into your existing CMP / cookie banner, in the advertising / targeting-cookies category:
// From your CMP's consent callback:
cmp.onConsentChange((consent) => {
if (consent.targetedAdvertising) {
specify.consentForEnhancedTracking();
} else {
specify.revokeEnhancedTrackingConsent();
}
});- Consent given → requests are credentialed, the cookie is set, wallet-less serving works.
- No consent → the SDK behaves exactly like v0.4.x: wallet-targeted serving only, no cookie sent or set, nothing degraded below today’s behaviour.
Consent is never persisted by the SDK — it lives on the client instance for the lifetime of the page. That’s deliberate: your CMP stays the single source of truth, so call the gate on every page load where the user has consented, and a withdrawal takes effect on the very next pageview. hasEnhancedTrackingConsent() tells you the current state if you need to assert it in tests.
If your site has no consent flow yet and you operate where one is required, add the prompt before enabling — the upgrade is still worth it for the non-consenting path alone, since you keep full v0.4.x behaviour with none of the legacy cache fragility.
Step 4 — Update your serve calls
The old signature is unchanged, so most integrations need no edits here. Three behavioural notes:
You can now serve without addresses, anywhere:
// Previously: no-fill unless cacheMostRecentAddress had a wallet banked.
// Now: fills whenever Specify recognises the browser (consented users).
const ad = await specify.serve([], {
imageFormat: ImageFormat.LONG_BANNER,
adUnitId: 'header-banner',
});In the browser, prefer the options-only form. It’s the same call with the address argument dropped, and it reads better at slots that don’t have an address to hand — the SDK serves against identified wallets, detected wallets, and the Enhanced Tracking cookie:
const ad = await specify.serve({
imageFormat: ImageFormat.LONG_BANNER,
adUnitId: 'header-banner',
});Both forms are supported and can be mixed freely across your placements.
Keep passing every wallet you know about when you have them. Wallets remain the strongest targeting signal, and each wallet-bearing serve strengthens the browser↔person link that powers wallet-less fills later. The multiple-wallets guidance from v0.4.x applies unchanged.
Step 5 — Add identify() on wallet connect (recommended)
If users connect wallets on pages where no placement renders, tell Specify at the moment of connection:
onWalletConnect((addresses) => specify.identify(addresses));Identified addresses accumulate on the client and merge into every later serve() call, so you only have to do this once per connection rather than threading addresses down to each slot. It costs nothing visually and improves recognition — and therefore fill — on every subsequent pageview.
Step 6 — Know what wallet detection does for you
In the browser, v1.0.0 passively detects wallets already connected to the page (EIP-6963, with a window.ethereum fallback) and includes them in every request. It’s on by default and it never prompts: detection calls the silent eth_accounts only, never eth_requestAccounts, so it cannot open a wallet or request permissions.
For most publishers this is free fill and needs no code. Two things to know:
specify.getDetectedWallets(); // what detection currently sees
// Opt out entirely:
const specify = new Specify({
publisherKey: process.env.NEXT_PUBLIC_SPECIFY_PUBLISHER_KEY,
privacy: { disableWalletDetection: true },
});WalletConnect v2 sessions don’t inject a provider, so remote wallets stay invisible to detection — pass those through identify() or serve(). And if you construct a client per view in a single-page app, call specify.destroy() on teardown to stop detection and detach its listeners.
Step 7 — Check your CSP / network allowlists
Requests now go to https://spfsrv.com with credentials. If you set a Content-Security-Policy, add it to connect-src:
Content-Security-Policy: connect-src ... https://spfsrv.com;CORS is handled on Specify’s side (your origin is reflected, Allow-Credentials is set). No proxy configuration is needed — and if you currently proxy SDK traffic through your own backend, remove that for the upgraded SDK, since a proxy strips the cookie and disables Enhanced Tracking.
Step 8 — Server-side callers
If you call serve() from your backend:
- Wallet-targeted serving continues to work exactly as before.
- Enhanced Tracking does not apply — the cookie lives in the visitor’s browser, which your server never sees. Wallet detection and the consent gate are browser-only for the same reason.
- As on v0.4.x, client-side calling remains strongly recommended (also for geo-compliance reasons). If server-side is unavoidable, talk to the Specify team.
Step 9 — Test, then ship
- Swap in your test key — it returns rotating sample ads and ignores moderation, as today.
- Verify the three states: wallet connected (ad), no wallet + consented + recognised browser (ad), no wallet + no consent (no ad, clean disappearance).
- Confirm your no-ad path still renders nothing — no empty boxes, no spinners.
- Swap in the production key.
Rollback
The legacy endpoint stays up, so rolling back is npm install @specify-sh/sdk@0.4.2 and restoring your previous code (documented in the v0.4.x reference). You’d lose Enhanced Tracking fill on wallet-less pageviews, and go back to the client-side cache for post-disconnect serving.
FAQ
Do I have to migrate? No, and there’s no announced end-of-life for v0.4.x. You’d just be leaving the wallet-less inventory unfilled.
Does anything change about payments or reporting? No.
Does the ad response change? No — SpecifyAd fields, the simplified-markdown content, image formats, and error classes are identical. Your rendering code carries over untouched.
Is the “Sponsored” label still my responsibility? Yes. On every placement, including wallet-less ones.
What data does the cookie contain? An opaque identifier only, set server-side on Specify’s domain. The SDK never reads, writes or sees its value. No fingerprinting, no reading of other site data. Full details in the Enhanced Tracking overview and the Terms of Service.