|
| 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