Specify docs

Google Tag Manager

Install the Specify SDK with a Custom HTML tag, connect consent, and serve an ad through the command queue.

Coming soon

The tag manager loader is not live yet. https://spfsrv.com/sdk/v1.js does not resolve, so the snippet below will not run if you paste it today.

To add ads now, install the Browser SDK instead. This page describes the loader as it will work, so you can plan the integration.

Use the Google Tag Manager (GTM) loader to run Specify on sites where tags are easier to update than application code. Any tag manager that can fire a Custom HTML tag can use the same setup.

The loader contains the same SDK as the npm package. It adds a gtag-style command queue and loads from Specify's edge.

Current version: v1.0.0. Loader URL: https://spfsrv.com/sdk/v1.js

GTM or npm?

Google Tag Managernpm (@specify-sh/publisher-sdk)
Best forContent sites, docs, blogs, and marketing pages where a marketer owns the tagsDapps and apps with a build step and a wallet layer
InstallPaste one Custom HTML tagnpm install, import, and deploy
Wallet addressesPass known addresses with identifyThe same options, plus serve(addresses, …)
RenderingWrite the DOM in the tagUse your framework's components
TypesNo bundled typesFull TypeScript types and error classes

Use the SDK if your site has a build step and a wallet integration. It provides TypeScript types and error classes. GTM suits pages without wallet UI because you can add it without changing the application bundle. Both integrations use the same serving edge and support Enhanced Tracking.

Install the loader

Paste this snippet into a Custom HTML tag that fires on All Pages. You can also add it to the page <head> without a tag manager.

<script>window.specify=window.specify||function(){(window.specify.q=window.specify.q||[]).push(arguments)};</script>
<script async src="https://spfsrv.com/sdk/v1.js"></script>

The first line creates a stub that stores commands in window.specify.q. The second loads the SDK asynchronously. Once loaded, the SDK replaces the stub with its dispatcher and runs the queued commands in order.

You can call specify(...) on the next line or from another tag without waiting for the download. Loading the script twice is safe because another copy detects the active dispatcher and exits.

Commands

Call specify() with a command name followed by its arguments.

CommandArgumentsBehavior
specify('init', config)The same config object as new Specify(config)Creates the singleton. Another init logs a warning and has no effect
specify('cookieConsent', granted)true or falseSets consent for Specify's identity cookie
specify('identify', addresses)One address or an array of addressesIncludes the addresses in later serves
specify('serve', options, callback)options is { imageFormat, adUnitId }Calls callback(ad, error) with the result

init

specify('init', { publisherKey: 'spk_your_publisher_key_here' });

The config object accepts every constructor option. Publisher keys are safe to include in browser code. See the SDK reference for all options.

serve

specify('serve', { imageFormat: 'LANDSCAPE', adUnitId: 'header-banner-1' }, function (ad, error) {
  if (error || !ad) return; // No fill, or a request error. Render nothing
  // render ad
});

The callback receives (ad, error). On success, ad contains the ad object or null when Specify has no ad, and error is undefined. On failure, ad is null and error is an Error. Render nothing when either case leaves you without an ad.

Pass imageFormat as 'LANDSCAPE', 'LONG_BANNER', or 'SHORT_BANNER'. The tag has no enum to import.

The command uses the SDK's options-only form. It considers wallets detected on the page, addresses passed to identify, and the Enhanced Tracking cookie after consent. If your callback throws, the loader logs the error and continues processing the queue.

Ordering and buffering

GTM does not guarantee tag order. The loader queues consent, revokeConsent, identify, and serve commands received before init, then runs them after init. A serve tag can therefore fire before the init tag.

window.Specify

The loader exposes the class as window.Specify. You can create and keep your own instance when the command queue does not fit the integration. Most GTM installations only need the queue.

Custom HTML tag example

This tag initialises Specify, reads consent from a CMP's dataLayer event, requests one ad, and renders it with a "Sponsored" label. It leaves the slot empty when no ad is available.

<div id="specify-ad"></div>

<script>window.specify=window.specify||function(){(window.specify.q=window.specify.q||[]).push(arguments)};</script>
<script async src="https://spfsrv.com/sdk/v1.js"></script>

<script>
  specify('init', { publisherKey: 'spk_your_publisher_key_here' });

  // Consent, from your CMP, on every page load. Most CMPs expose a callback
  // like this one; adapt it to whatever yours provides.
  window.myCmp.onConsentReady(function (consent) {
    specify(consent.targetedAdvertising ? 'consent' : 'revokeConsent');
  });

  // If your site already knows the connected wallet, hand it over:
  // specify('identify', '0x1111111111111111111111111111111111111111');

  specify('serve', { imageFormat: 'LONG_BANNER', adUnitId: 'gtm-header-banner' }, function (ad, error) {
    var slot = document.getElementById('specify-ad');
    if (!slot || error || !ad) {
      return; // No ad: leave the slot empty. No box, no spinner, no error state
    }

    var label = document.createElement('span');
    label.textContent = 'Sponsored';
    label.style.fontSize = '11px';
    label.style.textTransform = 'uppercase';
    label.style.opacity = '0.6';

    var link = document.createElement('a');
    link.href = ad.ctaUrl; // Use exactly as returned. Never modify or add params
    link.target = '_blank';
    link.rel = 'noopener noreferrer sponsored';

    if (ad.imageUrl) {
      var image = document.createElement('img');
      image.src = ad.imageUrl; // May be an animated GIF
      image.alt = ad.headline;
      image.style.maxWidth = '100%';
      link.appendChild(image);
    }

    var headline = document.createElement('h4');
    headline.textContent = ad.headline;

    var cta = document.createElement('span');
    cta.textContent = ad.ctaLabel;

    link.appendChild(headline);
    link.appendChild(cta);

    slot.appendChild(label);
    slot.appendChild(link);
  });
</script>

Every placement needs a "Sponsored" label. If Specify returns no ad, hide the slot without a placeholder, spinner, or error state. See Placements for sizing and positioning guidance.

Fire specify('consent') from your CMP's advertising or targeting-cookies category on every page load where the visitor has consented. If the CMP pushes events to dataLayer instead of calling a callback, create another Custom HTML tag containing specify('consent'); and trigger it from the CMP event. The queue handles either tag order.

The loader keeps consent only for the current pageview, leaving the CMP as the source of truth. Fire specify('revokeConsent') when the visitor withdraws consent so the change takes effect on the current page and later pageviews.

Ads can still serve without consent. Consent enables the server-side cookie that recognises a returning visitor on a page without a connected wallet. Without consent, visitors with a wallet on the page can still receive wallet-targeted ads, matching the behavior before Enhanced Tracking.

CSP

The loader needs script-src permission to fetch the script and connect-src permission to request ads:

Content-Security-Policy: script-src 'self' https://spfsrv.com; connect-src 'self' https://spfsrv.com;

Specify serves ad images and community logos from https://content.specify.sh and https://assets.specify.sh. Add both domains to img-src when your policy restricts images.

Specify reflects your origin and sets Allow-Credentials for CORS. Do not proxy the requests. A proxy strips the identity cookie and disables Enhanced Tracking.

On this page