Skip to content

Commit 6187766

Browse files
committed
Paywalls RTD Provider: add module and analytics adapter docs
Companion docs for prebid/Prebid.js PR #14541. - dev-docs/modules/paywallsRtdProvider.md: RTD module docs with refactored 3-scope ORTB2 (site/user/imp), bare domain iss, jws, mstk, pvtk - dev-docs/analytics/paywalls.md: Analytics adapter docs (gtag, dataLayer, callback output modes, sampling, activity controls)
1 parent 3bc803c commit 6187766

2 files changed

Lines changed: 370 additions & 0 deletions

File tree

dev-docs/analytics/paywalls.md

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
---
2+
layout: analytics
3+
title: Paywalls
4+
description: Paywalls Analytics Adapter
5+
modulecode: paywallsAnalyticsAdapter
6+
prebid_member: false
7+
---
8+
9+
#### Overview
10+
11+
The Paywalls Analytics Adapter emits [VAI (Validated Actor Inventory)](https://paywalls.net/docs/publishers/vai) classification on each Prebid auction. VAI helps publishers distinguish **human traffic** and **AI agents** from **non-human automation** (sharing/preview bots, search crawlers, AI training scrapers, etc.), enabling them to segment and analyze performance by traffic class (yield, fill, viewability, buyer outcomes) in their existing analytics stack (GA4, GTM / dataLayer, or a custom callback).
12+
13+
Two key-value pairs are emitted per auction:
14+
15+
{: .table .table-bordered .table-striped }
16+
17+
| Key | Example | Description |
18+
| :-- | :------ | :---------- |
19+
| `vai_vat` | `HUMAN` | Validated Actor Type — `HUMAN`, `AI_AGENT`, `SHARING`, or `OTHER` |
20+
| `vai_act` | `ACT-1` | Actor Confidence Tier — `ACT-1`, `ACT-2`, or `ACT-3` |
21+
22+
If VAI is unavailable, both values are `UNKNOWN`.
23+
24+
{: .alert.alert-info :}
25+
The companion [Paywalls RTD Provider](/dev-docs/modules/paywallsRtdProvider.html) injects VAI into ORTB2 and GAM targeting. The analytics adapter independently reads the same `window.__PW_VAI__` global and routes classification to your analytics tool of choice.
26+
27+
#### Build
28+
29+
```bash
30+
gulp build --modules=paywallsAnalyticsAdapter
31+
```
32+
33+
Or with the recommended RTD provider:
34+
35+
```bash
36+
gulp build --modules=rtdModule,paywallsRtdProvider,paywallsAnalyticsAdapter
37+
```
38+
39+
#### Analytics Options
40+
41+
{: .table .table-bordered .table-striped }
42+
43+
| Name | Type | Scope | Description | Default |
44+
| :--- | :--- | :---- | :---------- | :------ |
45+
| provider | `String` | Required | Must be `'paywalls'` ||
46+
| options.output | `String` | Optional | Output mode: `'gtag'`, `'dataLayer'`, or `'callback'` | `'callback'` |
47+
| options.scriptUrl | `String` | Optional | URL of the VAI loader script | `'/pw/vai.js'` |
48+
| options.samplingRate | `Number` | Optional | Fraction of page views that emit analytics (0.0–1.0) | `1.0` |
49+
| options.callback | `Function` | Optional | Called with the metrics object when output is `'callback'` | `null` |
50+
51+
#### Example Configuration
52+
53+
##### gtag (Google Analytics 4)
54+
55+
```javascript
56+
pbjs.enableAnalytics([{
57+
provider: 'paywalls',
58+
options: {
59+
output: 'gtag'
60+
}
61+
}]);
62+
```
63+
64+
Fires a GA4 event via the global `gtag()` function:
65+
66+
```javascript
67+
gtag('event', 'vai_auction', { vai_vat: 'HUMAN', vai_act: 'ACT-1' });
68+
```
69+
70+
##### dataLayer (Google Tag Manager)
71+
72+
```javascript
73+
pbjs.enableAnalytics([{
74+
provider: 'paywalls',
75+
options: {
76+
output: 'dataLayer'
77+
}
78+
}]);
79+
```
80+
81+
Pushes to the GTM `dataLayer` array:
82+
83+
```javascript
84+
window.dataLayer.push({
85+
event: 'vai_auction',
86+
vai_vat: 'HUMAN',
87+
vai_act: 'ACT-1'
88+
});
89+
```
90+
91+
In GTM, create a Custom Event trigger on `vai_auction` to route the data to any tag.
92+
93+
##### callback (Custom Function)
94+
95+
```javascript
96+
pbjs.enableAnalytics([{
97+
provider: 'paywalls',
98+
options: {
99+
output: 'callback',
100+
callback: function (metrics) {
101+
console.log(metrics);
102+
// { vai_vat: 'HUMAN', vai_act: 'ACT-1' }
103+
}
104+
}
105+
}]);
106+
```
107+
108+
#### Sampling
109+
110+
Set `samplingRate` to control cost. The decision is made once per page load — all auctions on that page either emit or don't.
111+
112+
```javascript
113+
pbjs.enableAnalytics([{
114+
provider: 'paywalls',
115+
options: {
116+
samplingRate: 0.1 // emit on ~10% of page views
117+
}
118+
}]);
119+
```
120+
121+
#### Activity Controls
122+
123+
The adapter uses `loadExternalScript` to inject `vai.js`. If your activity configuration restricts external scripts, allow the `paywalls` component:
124+
125+
```javascript
126+
pbjs.setConfig({
127+
allowActivities: {
128+
loadExternalScript: {
129+
default: false,
130+
rules: [{
131+
condition: function (params) {
132+
return params.componentName === 'paywalls';
133+
},
134+
allow: true
135+
}]
136+
}
137+
}
138+
});
139+
```
140+
141+
#### Privacy
142+
143+
VAI does not collect, store, or transmit user IDs, cookies, fingerprints, or PII. Classification is based on aggregate session-level behavioral signals processed entirely in the browser.
144+
145+
#### Registration
146+
147+
For questions or integration help, contact [engineering@paywalls.net](mailto:engineering@paywalls.net).
148+
149+
#### Further Reading
150+
151+
* [VAI Documentation](https://paywalls.net/docs/publishers/vai)
152+
* [Paywalls RTD Provider](/dev-docs/modules/paywallsRtdProvider.html)
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
---
2+
layout: page_v2
3+
title: Paywalls RTD Provider
4+
display_name: Paywalls RTD Provider
5+
description: VAI (Validated Actor Inventory) classification for Prebid.js — enriches ORTB2 and GAM targeting with actor-type and confidence signals.
6+
page_type: module
7+
module_type: rtd
8+
module_code: paywallsRtdProvider
9+
enable_download: true
10+
vendor_specific: true
11+
sidebarType: 1
12+
---
13+
14+
# Paywalls RTD Provider
15+
{:.no_toc}
16+
17+
The Paywalls RTD module integrates [VAI (Validated Actor Inventory)](https://paywalls.net/docs/publishers/vai) into Prebid.js. VAI helps publishers distinguish **human traffic** and **AI agents** from **non-human automation** (sharing/preview bots, search crawlers, AI training scrapers, etc.) so they can make better-informed economic decisions about their inventory.
18+
19+
Each page impression is classified by **actor type** (`vat`) and **confidence tier** (`act`), producing a cryptographically signed assertion that SSPs and DSPs can independently verify via a standard JWKS endpoint.
20+
21+
* TOC
22+
{:toc}
23+
24+
## Overview
25+
26+
The module automates VAI loading, timing, and signal injection:
27+
28+
* **ORTB2 enrichment** — VAI signals are split across `site.ext.vai` (domain provenance), `user.ext.vai` (actor classification + signed assertion), and `imp[].ext.vai` (pageview correlation), available to all ORTB2-native bid adapters.
29+
* **GAM targeting**`vai_vat` and `vai_act` key-value pairs are set per ad unit for Google Ad Manager line item targeting.
30+
* **Graceful degradation** — if VAI is unavailable or times out, the auction proceeds normally without enrichment.
31+
32+
## Integration
33+
34+
Build the Paywalls RTD module into your Prebid.js package:
35+
36+
```bash
37+
gulp build --modules=rtdModule,paywallsRtdProvider
38+
```
39+
40+
{: .alert.alert-info :}
41+
The global RTD module (`rtdModule`) is a required dependency.
42+
43+
## Configuration
44+
45+
This module is configured as part of the `realTimeData.dataProviders` object:
46+
47+
```javascript
48+
pbjs.setConfig({
49+
realTimeData: {
50+
auctionDelay: 500,
51+
dataProviders: [
52+
{
53+
name: 'paywalls',
54+
waitForIt: true,
55+
params: {
56+
scriptUrl: '/pw/vai.js'
57+
}
58+
}
59+
]
60+
}
61+
});
62+
```
63+
64+
### Parameter Descriptions
65+
66+
{: .table .table-bordered .table-striped }
67+
68+
| Name | Type | Scope | Description | Default |
69+
| :--- | :--- | :---- | :---------- | :------ |
70+
| name | `String` | Required | RTD sub module name | Always `'paywalls'` |
71+
| waitForIt | `Boolean` | Optional | Should be `true` when `auctionDelay` is set | `false` |
72+
| params | `Object` | Optional | Provider configuration | `{}` |
73+
| params.scriptUrl | `String` | Optional | URL of the VAI loader script | `'/pw/vai.js'` |
74+
| params.waitForIt | `Number` | Optional | Max ms to wait for VAI before releasing the auction (distinct from the Boolean `waitForIt` above) | `100` |
75+
76+
### Hosting Modes
77+
78+
VAI supports two hosting modes for the loader script:
79+
80+
* **Publisher-hosted** (preferred) — The script is served from the publisher's own domain via a CDN or server integration. Use the default relative path `'/pw/vai.js'`. This keeps requests same-origin, avoids CORS, and ensures the assertion's `dom` claim matches the inventory domain.
81+
* **Paywalls-hosted** — The script is served from `https://paywalls.net/pw/vai.js`. Set `scriptUrl` to the full URL. This requires paywalls.net configuration before usage. **Note:** The domain provenance claim (`dom`) will reflect `paywalls.net` rather than the inventory domain, which may affect SSP verification and buyer trust.
82+
83+
## Output
84+
85+
### ORTB2 — `site.ext.vai` (Domain Provenance)
86+
87+
Fields that describe the assertion context. The `dom` value can be cryptographically verified through the signed `jws` in `user.ext.vai`.
88+
89+
```json
90+
{
91+
"site": {
92+
"ext": {
93+
"vai": {
94+
"iss": "paywalls.net",
95+
"dom": "example.com"
96+
}
97+
}
98+
}
99+
}
100+
```
101+
102+
{: .table .table-bordered .table-striped }
103+
104+
| Field | Description |
105+
| :---- | :---------- |
106+
| `iss` | Issuer — bare domain (e.g. `paywalls.net`) |
107+
| `dom` | Domain the assertion covers |
108+
109+
### ORTB2 — `user.ext.vai` (Actor Classification)
110+
111+
Fields that describe the classified actor and the signed assertion:
112+
113+
```json
114+
{
115+
"user": {
116+
"ext": {
117+
"vai": {
118+
"iss": "paywalls.net",
119+
"mstk": "01J4X9K2ABCDEF01234567",
120+
"vat": "HUMAN",
121+
"act": "ACT-1",
122+
"jws": "eyJhbGciOiJFZERTQSIs..."
123+
}
124+
}
125+
}
126+
}
127+
```
128+
129+
{: .table .table-bordered .table-striped }
130+
131+
| Field | Description |
132+
| :---- | :---------- |
133+
| `iss` | Issuer — bare domain (e.g. `paywalls.net`) |
134+
| `vat` | Validated Actor Type — one of `HUMAN`, `AI_AGENT`, `SHARING`, `OTHER` |
135+
| `act` | Actor Confidence Tier — one of `ACT-1`, `ACT-2`, `ACT-3` |
136+
| `mstk` | Micro-session token — unique per assertion |
137+
| `jws` | Full JWS (compact serialization) for SSP/DSP verification |
138+
139+
### ORTB2 — `imp[].ext.vai` (Pageview Correlation)
140+
141+
Set on each ad unit’s `ortb2Imp` when `pvtk` is available from the VAI payload:
142+
143+
```json
144+
{
145+
"imp": [{
146+
"ext": {
147+
"vai": {
148+
"pvtk": "01J4X9K2ABCDEF01234567/3"
149+
}
150+
}
151+
}]
152+
}
153+
```
154+
155+
{: .table .table-bordered .table-striped }
156+
157+
| Field | Description |
158+
| :---- | :---------- |
159+
| `pvtk` | Pageview token — client-derived, unsigned; correlates impressions within a pageview using the `mstk` root |
160+
161+
### GAM Targeting
162+
163+
The module sets key-value pairs on every ad unit for Google Ad Manager targeting:
164+
165+
{: .table .table-bordered .table-striped }
166+
167+
| Key | Example Value | Description |
168+
| :-- | :------------ | :---------- |
169+
| `vai_vat` | `HUMAN` | Actor type |
170+
| `vai_act` | `ACT-1` | Confidence tier |
171+
172+
These are available via `pbjs.getAdserverTargeting()` and are compatible with standard GPT integration.
173+
174+
## Activity Controls
175+
176+
The module uses `loadExternalScript` to inject `vai.js`. If your activity configuration denies external scripts by default, explicitly allow the `paywalls` component:
177+
178+
```javascript
179+
pbjs.setConfig({
180+
allowActivities: {
181+
loadExternalScript: {
182+
default: false,
183+
rules: [
184+
{
185+
condition: function (params) {
186+
return params.componentName === 'paywalls';
187+
},
188+
allow: true
189+
}
190+
]
191+
}
192+
}
193+
});
194+
```
195+
196+
## Privacy
197+
198+
* **No user identifiers** — VAI does not collect, store, or transmit user IDs, cookies, or fingerprints.
199+
* **No PII** — The classification is based on aggregate session-level behavioral signals, not personal data.
200+
* **Browser-side only** — All signal extraction runs in the browser; no data leaves the page except the classification result.
201+
* **Signed assertions** — SSPs can independently verify the `jws` via the JWKS endpoint pulled from the JWS header (typically `https://example.com/pw/jwks.json`).
202+
203+
## How It Works
204+
205+
1. **`init()`** — Checks `window.__PW_VAI__` and `localStorage` for an existing VAI payload. If present and unexpired, caches it for immediate use.
206+
2. **`getBidRequestData()`** — If cached VAI exists, merges ORTB2 and calls back immediately (fast path). Otherwise, injects `vai.js` via `loadExternalScript`, sets up a callback hook, and polls until timeout (slow path). On timeout, the auction proceeds without enrichment.
207+
3. **`getTargetingData()`** — Returns `{ vai_vat, vai_act }` for each ad unit from the current VAI payload.
208+
209+
## Support
210+
211+
For questions or integration help, contact [engineering@paywalls.net](mailto:engineering@paywalls.net).
212+
213+
## Further Reading
214+
215+
* [VAI Documentation](https://paywalls.net/docs/publishers/vai)
216+
* [Paywalls Analytics Adapter](/dev-docs/analytics/paywalls.html)
217+
* [Prebid RTD Module Documentation](/dev-docs/add-rtd-submodule.html)
218+
* [How Bid Adapters Should Read First Party Data](/features/firstPartyData.html#how-bid-adapters-should-read-first-party-data)

0 commit comments

Comments
 (0)