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 Manager | npm (@specify-sh/publisher-sdk) | |
|---|---|---|
| Best for | Content sites, docs, blogs, and marketing pages where a marketer owns the tags | Dapps and apps with a build step and a wallet layer |
| Install | Paste one Custom HTML tag | npm install, import, and deploy |
| Wallet addresses | Pass known addresses with identify | The same options, plus serve(addresses, …) |
| Rendering | Write the DOM in the tag | Use your framework's components |
| Types | No bundled types | Full 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.
| Command | Arguments | Behavior |
|---|---|---|
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 false | Sets consent for Specify's identity cookie |
specify('identify', addresses) | One address or an array of addresses | Includes 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.
Consent
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.