Specify docs

Migrating from v0.4.x

Move an existing publisher integration from @specify-sh/sdk to @specify-sh/publisher-sdk v1.

Migrate with the wizard

npx @specify-sh/wizard --publisher

The wizard runs through the coding agent you already use (Claude Code, Codex etc.). It recognises a v0.4.x integration, proposes a migration plan from this guide and implements it as a reviewable diff.

Prefer to do it by hand? The steps below are the same migration.

Migrate manually

@specify-sh/sdk is deprecated. Its replacement is @specify-sh/publisher-sdk, which adds Enhanced Tracking: targeted ads for a visitor who has connected a wallet somewhere on the network, but not on the page they are looking at now.

This is a breaking change. Plan about an hour.

What changes

v0.4.xv1
Package@specify-sh/sdk@specify-sh/publisher-sdk
Endpointapp.specify.sh/api/adsspfsrv.com/v1/ads, called with credentials so the identity cookie can travel
Serving with no walletOnly through cacheMostRecentAddressThrough the identity cookie, gated on consent
Consentn/asetCookieConsent(granted), hasCookieConsent()
Wallet registrationn/aidentify(addresses)
Server-side callsSame clientSeparate entry: @specify-sh/publisher-sdk/server
serve()serve(addresses, options)Unchanged, plus serve(options) for the cookie-only case
Click redirect hostapp.specify.sh/r/…spfsrv.com/r/…. Old links redirect, and ctaUrl is always correct as returned

What is removed

  • cacheMostRecentAddress and the browser cache behind it. The identity cookie replaces it, and resolves across the network rather than only your site.
  • localId. The cookie is the durable handle now, so nothing is stored in localStorage.
  • walletAddress on the returned ad. After a cookie-only serve it could name a wallet you never sent, so it is no longer returned.
  • AuthenticationError, APIError and NotFoundError. ValidationError is the only error the SDK throws. Everything from the request onward returns null.
  • details on ValidationError. v0.4.x carried a field-level details array alongside the message. v1 does not, so read error.message.

One field is narrowed rather than removed: imageUrl is typed string | null in v1, where v0.4.x typed it string even though a NO_IMAGE placement has no image. TypeScript will point at anywhere you hand it straight to an <img>.

1. Swap the package

Terminal
npm uninstall @specify-sh/sdk
npm install @specify-sh/publisher-sdk

Then update your imports. Nothing else about the import shape changes.

-import Specify, { ImageFormat } from '@specify-sh/sdk';
+import Specify, { ImageFormat } from '@specify-sh/publisher-sdk';

2. Drop cacheMostRecentAddress

 const specify = new Specify({
   publisherKey: process.env.NEXT_PUBLIC_SPECIFY_PUBLISHER_KEY,
-  cacheMostRecentAddress: true,
 });

The option no longer exists. Consent plus the identity cookie covers the same case, and covers it across every site in the network rather than only yours.

The cookie is set on Specify's domain and is never read or set without your signal. Connect it to the advertising or targeting category in your consent platform:

lib/consent.ts
cmp.onConsentChange((consent) => {
  specify.setCookieConsent(consent.targetedAdvertising);
});

Consent starts false, and the SDK never stores it, so set it on every page load and again whenever the visitor changes their mind. Without consent the SDK behaves as v0.4.x did: it serves against the wallet addresses you pass, and neither reads nor sets the cookie.

Use hasCookieConsent() to assert the state in tests.

4. Serve without addresses

Your existing calls keep working unchanged. What is new is that a placement with no wallet can now fill. serve() accepts null or undefined in the address position, so the call site does not have to branch on whether a wallet is connected:

const ad = await specify.serve(wallet ?? null, {
  imageFormat: ImageFormat.LONG_BANNER,
  adUnitId: 'header-banner'
});

If you have no address at the call site at all, serve({ imageFormat }) is the same call without the first argument.

Keep passing every wallet you know about. Wallets remain the strongest targeting signal, and each serve that carries one strengthens the link used for later wallet-free serving.

5. Register wallets on connect

If people connect wallets on pages where no placement renders, tell Specify at the moment of connection:

lib/wallet.ts
onWalletConnect((addresses) => specify.identify(addresses));

Registered addresses accumulate and ride along on every later serve(), so deeper placements do not need the address passed down.

6. Move server-side calls

Serving from a backend now uses its own entry point, and there is no client to construct:

app/api/ad/route.ts
import { serve, ImageFormat } from '@specify-sh/publisher-sdk/server';

const ad = await serve({
  publisherKey: process.env.SPECIFY_PUBLISHER_KEY!,
  walletAddresses: addresses,
  imageFormat: ImageFormat.LANDSCAPE
});

Cookies, consent and the identity cookie are browser features and do not apply, so walletAddresses is required. See the Node.js SDK.

7. Update CSP and remove any proxy

Requests 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;

Specify reflects your origin and sets Allow-Credentials, so no proxy is needed. If you currently proxy SDK traffic through your backend, remove it: a proxy strips the cookie and disables Enhanced Tracking.

8. Test and ship

  1. Use your Development key to check that the integration builds and renders, as on v0.4.x.
  2. Switch to your production key before you test any cookie integrations as the Development key returns a sample ad and stops there.
  3. On the production key, check three states: wallet connected, no wallet with consent on a recognised browser, and no wallet without consent.
  4. Confirm your no-ad path still renders nothing, with no empty boxes or spinners.

Questions

  • Do I have to migrate? Eventually. @specify-sh/sdk is deprecated and will not receive new features, though existing integrations keep serving.

  • Does anything change about payments or reporting? No.

  • Does the ad response change? walletAddress is gone and imageUrl is now string | null. Every other field, the simplified-markdown content, and the image formats are unchanged.

  • Is the "Sponsored" label still my responsibility? Yes, on every placement, including wallet-free ones.

  • What is in the cookie? An encrypted identifier on Specify's domain. It carries no wallet address, and the SDK cannot read it. Specify does not fingerprint visitors. See On privacy and the Terms of Service.

On this page