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 automates VAI loading, timing, and signal injection:
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.vai_vat and vai_act key-value pairs are set per ad unit for Google Ad Manager line item targeting.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: {
auctionDelay: 500,
dataProviders: [
{
name: 'paywalls',
waitForIt: true,
params: {
scriptUrl: '/pw/vai.js'
}
}
]
}
});
| Name | Type | Scope | Description | Default |
|---|---|---|---|---|
| name | String |
Required | RTD sub module name | Always 'paywalls' |
| waitForIt | Boolean |
Optional | Should be true when auctionDelay is set |
false |
| params | Object |
Optional | Provider configuration | {} |
| params.scriptUrl | String |
Optional | URL of the VAI loader script | '/pw/vai.js' |
| params.waitForIt | Number |
Optional | Max ms to wait for VAI before releasing the auction (distinct from the Boolean waitForIt above) |
100 |
VAI supports two hosting modes for the loader script:
'/pw/vai.js'. This keeps requests same-origin, avoids CORS, and ensures the assertion’s dom claim matches the inventory domain.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.site.ext.vai (Domain Provenance)Fields that describe the assertion context. The dom value can be cryptographically verified through the signed jws in user.ext.vai.
{
"site": {
"ext": {
"vai": {
"iss": "paywalls.net",
"dom": "example.com"
}
}
}
}
| Field | Description |
|---|---|
iss |
Issuer — bare domain (e.g. paywalls.net) |
dom |
Domain the assertion covers |
user.ext.vai (Actor Classification)Fields that describe the classified actor and the signed assertion:
{
"user": {
"ext": {
"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.
The module uses loadExternalScript to inject vai.js. If your activity configuration denies external scripts by default, explicitly allow the paywalls component:
pbjs.setConfig({
allowActivities: {
loadExternalScript: {
default: false,
rules: [
{
condition: function (params) {
return params.componentName === 'paywalls';
},
allow: true
}
]
}
}
});
jws via the JWKS endpoint pulled from the JWS header (typically https://example.com/pw/jwks.json).init() — Checks window.__PW_VAI__ and localStorage for an existing VAI payload. If present and unexpired, caches it for immediate use.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.getTargetingData() — Returns { vai_vat, vai_act } for each ad unit from the current VAI payload.For questions or integration help, contact engineering@paywalls.net.