The Paywalls RTD module integrates VAI (Validated Actor Inventory) 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.
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.
The module reads VAI classification from window.__PW_VAI__ (populated by the publisher’s vai.js script) and injects it into bid requests and ad server targeting:
site.ext.data.vai (domain provenance), user.ext.data.vai (actor classification + signed assertion), and imp[].ext.vai (pageview correlation), available to all ORTB2-native bid adapters.vai_vat and vai_act key-value pairs are set per ad unit for Google Ad Manager line item targeting.Load vai.js before Prebid.js initializes so that window.__PW_VAI__ is populated when the RTD module runs:
<script src="/pw/vai.js"></script>
<script src="prebid.js"></script>
The module does not inject vai.js itself. Publishers must add the script tag to their page. See VAI Documentation for setup details.
Build the Paywalls RTD module into your Prebid.js package:
gulp build --modules=rtdModule,paywallsRtdProvider
The global RTD module (rtdModule) is a required dependency.
This module is configured as part of the realTimeData.dataProviders object:
pbjs.setConfig({
realTimeData: {
dataProviders: [
{
name: 'paywalls'
}
]
}
});
| Name | Type | Scope | Description | Default |
|---|---|---|---|---|
| name | String |
Required | RTD sub module name | Always 'paywalls' |
site.ext.data.vai (Domain Provenance)Fields that describe the assertion context. The dom value can be cryptographically verified through the signed jws in user.ext.data.vai.
{
"site": {
"ext": {
"data": {
"vai": {
"iss": "paywalls.net",
"dom": "example.com"
}
}
}
}
}
| Field | Description |
|---|---|
iss |
Issuer — bare domain (e.g. paywalls.net) |
dom |
Domain the assertion covers |
user.ext.data.vai (Actor Classification)Fields that describe the classified actor and the signed assertion:
{
"user": {
"ext": {
"data": {
"vai": {
"iss": "paywalls.net",
"mstk": "01J4X9K2ABCDEF01234567",
"vat": "HUMAN",
"act": "ACT-1",
"jws": "eyJhbGciOiJFZERTQSIs..."
}
}
}
}
}
| Field | Description |
|---|---|
iss |
Issuer — bare domain (e.g. paywalls.net) |
vat |
Validated Actor Type — one of HUMAN, AI_AGENT, SHARING, OTHER |
act |
Actor Confidence Tier — one of ACT-1, ACT-2, ACT-3 |
mstk |
Micro-session token — unique per assertion |
jws |
Full JWS (compact serialization) for SSP/DSP verification |
imp[].ext.vai (Pageview Correlation)Set on each ad unit’s ortb2Imp when pvtk is available from the VAI payload:
{
"imp": [{
"ext": {
"vai": {
"pvtk": "01J4X9K2ABCDEF01234567/3"
}
}
}]
}
| Field | Description |
|---|---|
pvtk |
Pageview token — client-derived, unsigned; correlates impressions within a pageview using the mstk root |
The module sets key-value pairs on every ad unit for Google Ad Manager targeting:
| Key | Example Value | Description |
|---|---|---|
vai_vat |
HUMAN |
Actor type |
vai_act |
ACT-1 |
Confidence tier |
These are available via pbjs.getAdserverTargeting() and are compatible with standard GPT integration.
init() — Always returns true; the module is always available.getBidRequestData() — Reads window.__PW_VAI__. If a valid, unexpired payload is found, merges ORTB2 fragments (site.ext.data.vai, user.ext.data.vai, imp[].ext.vai) and calls back immediately. If VAI is absent or invalid, calls back without enrichment.getTargetingData() — Returns { vai_vat, vai_act } for each ad unit from the current VAI payload.jws via the JWKS endpoint pulled from the JWS header (typically https://example.com/pw/jwks.json).For questions or integration help, contact engineering@paywalls.net.