Docs
The tag: page safety and debugging
PubTrust is a third-party script on somebody else's page. This is exactly what it does there, what stops it doing harm, and how to look inside a running copy.
How is the tag delivered?
As one file per Site from cdn.pubtrust.cc/t/<tagKey>.js: the Site's configuration, assigned to window.__PT_CONFIG__, followed by the tag engine, which is identical for every publisher. The engine is 11.4 KB gzipped. If the tag key is unknown or inactive, if the request comes from a domain not registered to the Site, or if the edge itself fails, it serves a valid script that does nothing — never an error — so a misconfiguration cannot put an error in your console or break your page.
What stops the tag from breaking my page?
Every callback, observer and patched function the tag installs runs inside a guard that catches any exception, so an error inside PubTrust never propagates into your code. Around that guard:
- Error watchdog. After 8 caught errors in a page view, the tag stands down.
- CPU budget. Time spent inside the tag's guarded code is measured with
performance.now(). Past 60 ms in a page view, the tag stands down rather than become the performance problem it is meant to prevent. - Complete teardown. Standing down removes every hook, listener, observer, stylesheet and overlay the tag added, in reverse order of installation. A patched method is restored only if nothing else has patched it since, so another vendor's hook is never silently removed.
Function.prototype.toStringfidelity. A patched method reports the original function's source, so other scripts' integrity checks see no difference.- Reported, not silent. When the tag stands down it records why —
error_threshold,cpu_budget,remote_killormanual— and sends the reason with the page view's summary beacon. - Remote kill switch. A running tag polls its configuration every 5 minutes; tags on a killed Site stand down at their next poll.
- Your escape hatch. Set
window.__ptDisable = truebefore the tag loads and it never initialises. - Loaded once. A second copy of the tag on the same page does nothing.
What does the tag patch or listen to?
Only what Runtime Integrity and ad-stack integration need, and each Runtime Integrity hook is a separate per-Site setting. On a Site in Monitor the hooks detect and report only — nothing on the page is changed.
| Surface | What PubTrust does |
|---|---|
window.open | Detects a call with no recent genuine user gesture, or a second window from one gesture. In Block the call returns null; in Monitor it is reported and proceeds. |
history.pushState, history.replaceState | Reports a change to a different site made without a gesture. Same-site route changes are ignored. |
document.write, document.writeln | Detects calls made after the page has finished loading, which would otherwise wipe the page. In Block the call is refused; in Monitor it is reported and proceeds. |
HTMLMediaElement.prototype.play | Detects media that starts playing unmuted without a gesture. In Block it is muted; in Monitor it is reported. |
Capture-phase pointerdown, mousedown, click, touchstart, keydown | Records only the time of genuine (isTrusted) gestures — never what was typed or clicked. Detects script-synthesised clicks on links targeting the top or parent frame, and cancels them in Block. |
beforeunload, pagehide, visibilitychange | Reports navigation away without a gesture; sends the summary beacon. |
PerformanceObserver (long tasks) | Reports long tasks as a page-level signal. |
Prebid.js, GPT, window.__ptSlots | Subscribes to bid and render events through pbjs.que and googletag.cmd; replaces window.__ptSlots.push so bridge entries are judged as they arrive, and renders entries marked render: true through inert-park. |
window.__ptQ | Runs callbacks queued before the tag loaded, passing each the window.__pubtrust API, then runs later ones immediately. |
| Ad iframes (opt-in, Block only) | On a Site in Block, adds sandbox="allow-scripts allow-same-origin allow-popups allow-forms" — without allow-top-navigation — to iframes that have not loaded yet. An existing sandbox attribute is never widened. |
What network requests does the tag make?
Three kinds, to two hosts. From cdn.pubtrust.cc it fetches the full policy bundle once and polls its configuration every 5 minutes. To in.pubtrust.cc it sends beacons: violation beacons are batched and flushed within 2 seconds of a catch, at most 50 events per beacon, and one summary beacon is sent per page view when the page is hidden or unloaded. Beacons use navigator.sendBeacon with a text/plain body, which keeps them CORS simple requests with no preflight, falling back to fetch with keepalive and then to an image request. What the beacons contain is set out in Privacy.
How do I inspect a running tag?
Open the browser console on a page with the tag and read window.__pubtrust. It exists once the tag has started; if it is undefined, the tag did not initialise — the page has no configuration, the Site is Off, window.__ptDisable is set, or the edge served an inert script.
| Member | Returns |
|---|---|
api | The integration API version. |
v | The tag build version. |
site | The Site's tag key. |
mode() | The current Site mode: 'off', 'monitor' or 'block'. It changes if a configuration poll changes it. |
policies() | One { id, pack, mode, rules } entry per policy, where rules is the number of rules currently loaded — it grows when the full bundle arrives. |
disabled() | true once the tag has stood down. |
errors() | The most recent errors the tag caught (up to five), and clears them. |
off() | Stands the tag down for this page view, removing every hook. |
rulesLoaded() | true once the full policy bundle has been applied, or has failed to load and the tag is running on its inline rules. |
judge(input, options?) | A synchronous verdict for an ad — see the integration API. |
failOpen(reason, detail?) | Reports that your integration let an ad render unjudged, so it counts towards the fail-open rate. |
window.__pubtrust.mode(); // 'off' | 'monitor' | 'block'
window.__pubtrust.policies(); // [{ id, pack, mode, rules }, …]
window.__pubtrust.disabled(); // false while the tag is running
window.__pubtrust.errors(); // caught errors since the last call
window.__pubtrust.off(); // stand down for this page viewoff() affects only the page view you run it in. To stop the tag for every reader, switch the Site to Off in the dashboard.
How can my own code ask PubTrust for a verdict?
Call window.__pubtrust.judge() before you render a creative. It is synchronous — the answer comes from the rules and verdict cache already in memory, with no network request — and it never throws. Pass whatever you know: the markup as ad (or adm), vastXml, adomain or advertiserDomains, creativeId, bidder, dealId, size or width and height, slotId, landingUrl, cpm and currency. Pass Prebid's adId as id and the tag's own Prebid hook will not judge or report the same bid a second time.
window.__ptQ = window.__ptQ || [];
window.__ptQ.push(function (pt) {
var verdict = pt.judge({
ad: bid.ad,
advertiserDomains: bid.meta && bid.meta.advertiserDomains,
creativeId: bid.creativeId,
bidder: bid.bidder,
cpm: bid.cpm,
currency: bid.currency,
id: bid.adId
});
if (verdict.block) {
// do not render this creative
}
});window.__ptQ runs the callback as soon as the tag is ready, so there is no need to poll for window.__pubtrust.| Verdict field | Meaning |
|---|---|
violation | A policy matched. |
block | Refuse to render. true only when both the Site and the policy that matched are in Block — the Site mode is a ceiling. |
mode | 'block' when blocking, 'monitor' for a violation that is only reported, otherwise the Site mode. |
policyId, pack, severity | Which policy matched, and how severe it is. |
rule, ruleKind, lang | The literal value that matched, its kind and its language. |
layer, dl | The detection layer, and the decision time in milliseconds. |
fallbackId | The fallback creative attached to the policy, when there is one. |
reason | Present only when no judgement could be made — 'disabled', 'error' or 'not_ready'. Such a verdict never blocks. |
Options: { report: false } suppresses the violation beacon for this call; { impression: true } also counts a clean verdict as an impression — off by default, because a clean bid is not an impression until it wins; { layer } attributes the decision to a detection layer other than 1.
How does inert-park work?
When you hand PubTrust creative markup through window.__ptSlots with render: true, the creative is never allowed to execute before it is judged. The markup is parked as text inside <template><xmp> in the slot — a context in which no script runs, no image loads and no request fires — and judged, waiting up to 750 ms for the full rule bundle if it has not arrived yet. It is then either written into a same-origin iframe in the slot, or discarded and replaced with your fallback creative. In Monitor, a violating creative is rendered after judgement and reported.
Parking holds revenue, so it is capped: after 5,000 ms the creative renders unjudged. Every fail-open — a timeout, an error, a stood-down tag, a failed render, or a slot element that could not be found — is reported as an error event named fail_open:<reason>, so the fail-open rate is a number you can see. If the slot element cannot be found, the entry is judged and reported but not rendered, and your own code keeps the render path.