Skip to content

Ads

Prove to your advertising partners that an impression or a click came from a real person. You tell the SDK when an ad is on screen, clicked and gone; it watches whether the user actually touched the ad, and signs and sends one proof for each to DEA's recording service, which keeps them. Each ad gets its own ad session at DEA, which is not the session the SDK scores.

The SDK does not request, render or wrap ads. Your ad stack keeps doing that.

When to use it

  • You sell ad inventory and want to show buyers verified human traffic
  • You want invalid clicks (claimed with no touch on the ad) filtered out

Prerequisites

  • The SDK is installed and initialized with your appId and appKey. See the Quick Start. On a production appId without appKey, no proof is signed.
  • The page is served over HTTPS, and your Content Security Policy allows https://poh-api.dep-protocol.com in connect-src — see Content Security Policy.
  • The browser's key is registered: call attest() and get 'passed' before you open an ad session. The SDK does not register the key by itself, and DEA refuses every proof signed by a key it has not registered.

Usage

1. Open the ad session when the ad is on screen

import { DeaSdk } from '@dea/sdk';

const dea = DeaSdk.current!;
const container = document.querySelector('#ad-slot')!;

const attestation = await dea.attest(); // answers at once while the last registration is valid
const ad = attestation.verdict === 'passed'
  ? await dea.ads.session_opened(container, 'homepage_top')
  : null;

session_opened(surface?, adUnit?) signs and sends the impression. The DEA service answers with the ad session's id, ad.id; the SDK never makes one. Pass the element the ad renders into, so clicks can be checked against real touches, and the ad unit it loaded into. The ad unit is signed into the impression, the click and the end. Touches on the element count from the moment you call session_opened(), before it resolves.

The SDK does not read the classification before it records an ad: a 'bot' result does not stop the impression, and the score inside the proof tells the service how human the visit was. But no proof is signed until the session is sufficient: until the user has made as many actions as the model asks for — with the built-in model, 20 in a desktop browser, from at least two kinds of input (pointer, scroll, keyboard) with at least three each, and 8 in a browser on a phone or tablet (see Evidence). Until then, session_opened() resolves null and the impression is not recorded; the SDK does not keep it to send later. An ad shown at page load, before the user has interacted, is therefore not recorded.

When an impression has to be recorded, read getClassification().result before you call session_opened(). While it is 'insufficient', check again later, for example on the user's next interaction, and open the ad session once it is not if the ad is still on screen. After a null, a higher ads.counts.notScored than before the call means this was the cause.

session_opened() resolves null when no ad session could be opened: no appKey, no browser key, a session that is not sufficient yet, or the service did not accept the impression. When the network or the service fails, the SDK tries the impression up to five times within the 90 seconds its proof is valid, so session_opened() can take up to that long to resolve. To find out which case it was, see Find out why no ad session opened.

2. Declare the click and the end

container.addEventListener('click', () => ad?.click()); // the user clicked it
ad?.end(); // the ad is gone
  • click() signs and sends a click. If you passed an element and nothing touched it, the click is refused and click() returns false. One click per ad session.
  • end() signs and sends the end of the ad session, and releases the element. Later click() calls are refused.

The next ad is a new session_opened() call, with a new id. The SDK watches one element at a time: opening an ad session for a second ad stops watching the first, and a click on the first is then accepted without a check.

Warning: The SDK sees input on your page's own document only. A press inside an <iframe> — where Google Publisher Tag and many other ad servers render the creative — reaches neither the SDK nor a click listener on the container. For such a creative, the SDK cannot witness a touch, and click() on an ad session opened with the container is refused.

Note: end() signs and sends its proof asynchronously. Called while the page is unloading (pagehide), the request can be cancelled by the browser before it is sent. Call end() while the page is still open — when the ad is removed or replaced.

Find out why no ad session opened

session_opened() resolves null without a reason. The production SDK writes no log, but ads.counts moves on each attempt; compare it before and after the call:

import { DeaSdk, type AdCounts } from '@dea/sdk';

const dea = DeaSdk.current!;
const before: AdCounts = dea.ads.counts;
const ad = await dea.ads.session_opened(document.querySelector('#ad-slot')!, 'homepage_top');

if (ad === null) {
  const after = dea.ads.counts;
  if (dea.appKey === null) {
    console.warn('No appKey: nothing is signed on a production appId');
  } else if (after.notScored > before.notScored) {
    console.warn('The session is not sufficient yet: the impression was not recorded');
  } else if (after.refused > before.refused) {
    console.warn('Nothing signed: no browser key, or the scoring engine is not running');
  } else if (after.delivery.dropped > before.delivery.dropped) {
    console.warn('DEA refused the impression, or could not be reached before the proof expired');
  } else if (after.delivery.sent > before.delivery.sent) {
    console.warn('DEA accepted the impression but answered no ad session id');
  }
}
What moved Why no ad session opened
dea.appKey is null A production appId with no appKey: nothing is signed or sent
notScored The session is not sufficient yet (getClassification().result is 'insufficient'), so the impression was not signed or recorded. Open the ad session again once it is sufficient, if the ad is still on screen
refused No browser key (IndexedDB refused, no Web Crypto), or the scoring engine is not running — check dea.error
delivery.dropped DEA refused the impression — for example because the browser's key is not registered (call attest() first) or appKey is not the key issued with this appId — or DEA could not be reached before the proof expired
delivery.sent DEA accepted the impression but its answer carried no ad session id

Read the counts right after the call resolves. Two session_opened() calls running at once move the same counts.

The staging package (see Verify your setup) also logs each attempt: ad.impression with outcome refused and a reason such as not_scored or app_key_missing, and proof.rejected with the HTTP status DEA answered.

Examples

A banner with Google Publisher Tag

Google Publisher Tag renders the creative in an iframe, so its clicks cannot be declared from the page (see the warning above). This example records the impression and the end:

import { DeaSdk, type AdSession } from '@dea/sdk';

let ad: AdSession | null = null;

googletag.cmd.push(() => {
  const slot = googletag.defineSlot('/1234/homepage_top', [728, 90], 'ad-slot')!
    .addService(googletag.pubads());

  googletag.pubads().addEventListener('slotRenderEnded', async (event) => {
    if (event.slot !== slot) return;
    ad?.end(); // the slot rendered again: the previous ad is gone
    ad = null;
    if (event.isEmpty) return;

    const dea = DeaSdk.current!;
    const attestation = await dea.attest();
    if (attestation.verdict === 'passed') {
      ad = await dea.ads.session_opened(undefined, '/1234/homepage_top');
    }
  });

  googletag.enableServices();
  googletag.display('ad-slot');
});

The ad session is opened without an element: the SDK cannot see touches inside the iframe, so there is nothing to witness. An ad that renders before the session is sufficient is not recorded: see Open the ad session when the ad is on screen.

API

Member Returns Description
ads.session_opened(surface?, adUnit?) Promise<AdSession \| null> The ad is on screen; sends the impression. null while the session is not sufficient
ads.counts AdCounts Proofs signed, refused, not scored yet, unwitnessed clicks, delivery
ad.id string The ad session's id, made by the DEA service
ad.click() boolean Declares a click; sends it unless it is refused
ad.end() void Declares the ad gone; sends the end

AdCounts:

Field Type Description
signed number Proofs signed, whether or not DEA accepted them
refused number Proofs not signed: no appKey, no browser key, or the scoring engine is not running or refused
notScored number Proofs not signed because the session was not sufficient yet
unwitnessed number Clicks refused because nothing touched the watched element
delivery.sent number Proofs DEA accepted
delivery.dropped number Proofs given up on: DEA refused them, or every attempt failed before they expired
delivery.pending number Proofs being sent, or waiting to be retried