Skip to content

Paywalls Modules: VAI RTD Provider and Analytics Adapter#14541

Open
paywalls-mike wants to merge 25 commits intoprebid:masterfrom
paywalls-net:agent/paywalls-rtd-provider
Open

Paywalls Modules: VAI RTD Provider and Analytics Adapter#14541
paywalls-mike wants to merge 25 commits intoprebid:masterfrom
paywalls-net:agent/paywalls-rtd-provider

Conversation

@paywalls-mike
Copy link

@paywalls-mike paywalls-mike commented Mar 1, 2026

Type of issue

New module (RTD Provider + Analytics Adapter)

Description

Adds two new Paywalls modules that integrate VAI (Validated Actor Inventory) into Prebid.js:

Paywalls RTD Provider (paywallsRtdProvider.js) — Automates VAI loading, timing, and signal injection into the bid stream:

  • ORTB2 enrichment: site.ext.vai (domain provenance + signed assertion) and user.ext.vai (actor classification)
  • GAM targeting: vai_vat and vai_act key-value pairs per ad unit
  • Graceful degradation: if VAI is unavailable or times out, the auction proceeds normally

Paywalls Analytics Adapter (paywallsAnalyticsAdapter.js) — Emits VAI classification (vat, act) per auction to the publisher's analytics pipeline (GA4 gtag, GTM dataLayer, or custom callback), enabling publishers to segment performance by traffic class in their existing reporting stack.

What is VAI?

VAI classifies page impressions by actor type (human, AI agent, sharing/preview bot, other) and confidence tier (ACT-1 through ACT-3), producing a cryptographically signed JWS assertion that SSPs and DSPs can independently verify. No user identifiers, PII, cookies, or fingerprints are involved — classification is based on aggregate session-level behavioral signals processed entirely in the browser.

ORTB2 placement

site.ext.data.vai  → { iss, dom }              (domain provenance)
user.ext.data.vai  → { iss, vat, act, mstk, jws }  (actor classification)
imp[].ext.vai      → { pvtk }                  (pageview token)

Test Results

Lint

$ npx eslint --cache --cache-strategy content modules/paywallsRtdProvider.js modules/paywallsAnalyticsAdapter.js test/spec/modules/paywallsRtdProvider_spec.js test/spec/modules/paywallsAnalyticsAdapter_spec.js
(no output — clean)

Unit Tests

RTD Provider:  ✔ 22 tests completed
Analytics Adapter: ✔ 29 tests completed

Coverage

Module Statements Branches Functions Lines
paywallsRtdProvider.js 90% 83.78% 100% 91.76%
paywallsAnalyticsAdapter.js 93.9% 85.36% 100% 93.58%

All metrics exceed the 80% threshold.

Files Changed

File Description
modules/paywallsRtdProvider.js RTD submodule: VAI loading, ORTB2 injection, GAM targeting
modules/paywallsRtdProvider.md RTD module documentation (config, ORTB2 output, hosting modes)
modules/paywallsAnalyticsAdapter.js Analytics adapter: VAI emission via gtag/dataLayer/callback
modules/paywallsAnalyticsAdapter.md Analytics adapter documentation
test/spec/modules/paywallsRtdProvider_spec.js RTD provider unit tests (22 tests)
test/spec/modules/paywallsAnalyticsAdapter_spec.js Analytics adapter unit tests (29 tests)
integrationExamples/gpt/paywallsRtdProvider_example.html E2E integration test page for RTD
integrationExamples/gpt/paywallsAnalyticsAdapter_example.html E2E integration test page for analytics
integrationExamples/gpt/mock-vai.js Mock VAI script for integration testing

External Script Loading

None. Publishers must coordinate the deployment of the vai.js tag prior to enabling these prebid.js modules

Privacy

  • No user identifiers, PII, cookies, or fingerprints
  • Browser-side only — no data leaves the page except the classification result
  • Signed JWS assertions verifiable via public JWKS endpoint
  • No consent-gated data collection required

Steps to reproduce / test

# Build
gulp build --modules=rtdModule,paywallsRtdProvider,paywallsAnalyticsAdapter,appnexusBidAdapter

# Run unit tests
gulp test --nolint --file test/spec/modules/paywallsRtdProvider_spec.js
gulp test --nolint --file test/spec/modules/paywallsAnalyticsAdapter_spec.js

# Integration test
gulp serve-fast --modules=rtdModule,paywallsRtdProvider,paywallsAnalyticsAdapter,appnexusBidAdapter
# Open http://localhost:9999/integrationExamples/gpt/paywallsRtdProvider_example.html
# Open http://localhost:9999/integrationExamples/gpt/paywallsAnalyticsAdapter_example.html

Platform details

  • Prebid.js: current master (rebased)
  • Node.js: >=20
  • Tested on Chrome Headless 145 / macOS

Other information

Copilot AI review requested due to automatic review settings March 1, 2026 21:15
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dc9bc90910

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

- Register RTD submodule 'paywalls' with init, getBidRequestData, getTargetingData
- ORTB2 split: site.ext.vai (iss, aud, dom, kid, assertion_jws) + user.ext.vai (vat, act)
- Dynamic vai.js injection via loadExternalScript with poll/hook/timeout
- Graceful degradation: never blocks the auction
- 22 unit tests covering all acceptance criteria
- Added to approvedLoadExternalScriptPaths whitelist
- paywallsRtdProvider_example.html: E2E test with 5 assertions
  (ORTB2 site/user enrichment, GAM targeting, timing budget, VAI load)
- mock-vai.js: local VAI mock for self-contained testing
- Supports ?real mode (real vai.js from worker) and ?degrade mode
  (verifies graceful degradation with unreachable script)
- Overview, build instructions, configuration parameters
- ORTB2 split placement: site.ext.vai (provenance) + user.ext.vai (classification)
- GAM targeting keys (vai_vat, vai_act)
- Activity Controls, privacy section, how-it-works
- Testing instructions for unit and integration tests
- Publisher-hosted is preferred (same-origin, no CORS, dom claim match)
- Paywalls-hosted uses paywalls.net (not cdn.paywalls.net)
- Clarify CDN/server integration vs reverse proxy
- Correct activity name: loadExternalScript (not fetchBids)
- Update example default to false (realistic scenario)
- Editorial refinements
- Fix broken VAI URL in RTD provider links section (docs.publishers → docs/publishers)
- Normalize all VAI URLs to https://docs.paywalls.net/publishers/vai
- Align RTD provider heading structure with analytics adapter (# Overview)
- Clarify params.waitForIt vs top-level waitForIt distinction
- Document __PW_VAI_HOOK__, exp checking, and localStorage key in How It Works
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Paywalls modules to integrate VAI (Validated Actor Inventory) into Prebid.js auctions, enriching ORTB2/GAM targeting via an RTD provider and emitting per-auction classification via an analytics adapter.

Changes:

  • Introduces paywallsRtdProvider RTD submodule to load VAI and merge site.ext.vai / user.ext.vai into ORTB2 plus vai_vat / vai_act into GAM targeting.
  • Introduces paywallsAnalyticsAdapter to emit vai_vat / vai_act per auction via gtag, dataLayer, or callback (with sampling + de-dup).
  • Adds module docs, unit tests, integration example pages + mock VAI script, and updates the ESLint allowlist for loadExternalScript usage.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
modules/paywallsRtdProvider.js New RTD provider implementing VAI discovery/loading and ORTB2 + targeting enrichment.
modules/paywallsRtdProvider.md Documentation for RTD config, ORTB2 output, GAM targeting, and activity controls.
modules/paywallsAnalyticsAdapter.js New analytics adapter emitting VAI classification per auction to publisher analytics destinations.
modules/paywallsAnalyticsAdapter.md Documentation for analytics adapter config, output modes, sampling, and privacy notes.
test/spec/modules/paywallsRtdProvider_spec.js Unit tests for VAI payload detection, ORTB2 merge behavior, targeting, and timeouts.
test/spec/modules/paywallsAnalyticsAdapter_spec.js Unit tests for classification reading, emission modes, sampling, and de-dup behavior.
plugins/eslint/approvedLoadExternalScriptPaths.js Approves the two new modules for loadExternalScript.
integrationExamples/gpt/paywallsRtdProvider_example.html E2E example page for RTD provider behavior (including degrade/real modes).
integrationExamples/gpt/paywallsAnalyticsAdapter_example.html E2E example page for analytics emission behavior (including degrade/real modes).
integrationExamples/gpt/mock-vai.js Mock VAI script used by integration examples to simulate vai.js.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@paywalls-mike paywalls-mike force-pushed the agent/paywalls-rtd-provider branch from dc9bc90 to c2e15ae Compare March 1, 2026 21:22
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c2e15aea5d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@paywalls-mike
Copy link
Author

paywalls-mike commented Mar 1, 2026

Addressed automated review feedback in commit de4cf00.

Implemented fixes:

  • RTD: preserve late hook-delivered VAI payloads after timeout for subsequent auctions
  • RTD: respect explicit waitForIt=0 and validate waitForIt type/range
  • RTD: clean up timers/hook lifecycle and preserve/chain pre-existing PW_VAI_HOOK
  • Analytics: only skip injection when classification is valid (not just truthy PW_VAI)
  • Analytics: reject expired VAI payloads (exp) to align with RTD behavior
  • Analytics: clamp samplingRate to [0,1]
  • Analytics: use Object.create(null) for emittedAuctions map safety
  • Docs: corrected init() comment to match caching behavior

Regression coverage added for these behaviors.

Validation run locally:

  • npx gulp test --nolint --file test/spec/modules/paywallsRtdProvider_spec.js (24 passing)\n- npx gulp test --nolint --file test/spec/modules/paywallsAnalyticsAdapter_spec.js (32 passing)
  • npx eslint --cache --cache-strategy content modules/paywallsRtdProvider.js modules/paywallsAnalyticsAdapter.js test/spec/modules/paywallsRtdProvider_spec.js test/spec/modules/paywallsAnalyticsAdapter_spec.js (clean)

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: de4cf00754

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@paywalls-mike paywalls-mike force-pushed the agent/paywalls-rtd-provider branch from de4cf00 to 12b1099 Compare March 1, 2026 22:02
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12b1099de4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@paywalls-mike
Copy link
Author

Companion docs PR: prebid/prebid.github.io#6457

- P1: Store late VAI hook payloads for subsequent auctions (was dropping permanently after timeout)
- Fix waitForIt=0 treated as falsy (use typeof check instead of ||)
- Clean up timers and hook in resolve() to avoid leaking globals
- Preserve existing __PW_VAI_HOOK__ instead of clobbering
- Fix ensureVai skipping injection when __PW_VAI__ is truthy but invalid
- Clamp samplingRate to [0,1] with warning
- Use Object.create(null) for emittedAuctions map
- Fix init() JSDoc to match actual behavior (caches, not injects)
- Add regression tests for all bug fixes
@paywalls-mike paywalls-mike force-pushed the agent/paywalls-rtd-provider branch from 12b1099 to d145be8 Compare March 2, 2026 00:32
- buildOrtb2 now uses 3-scope structure:
  site.ext.vai: { iss, dom }
  user.ext.vai: { iss, vat, act, mstk, jws }
  imp[].ext.vai: { pvtk } (when available)
- Removed aud, kid, assertion_jws from ORTB2 injection
- iss uses bare domain (paywalls.net)
- mergeOrtb2Fragments enriches adUnit.ortb2Imp with pvtk
- paywallsRtdProvider.md: rewritten ORTB2 Output section (3 scopes)
- paywallsAnalyticsAdapter.md: updated scope refs, assertion_jws→jws
- Tests: updated MOCK_VAI fixture, buildOrtb2 assertions, added pvtk tests
- 27 RTD tests pass, 33 analytics tests pass, lint clean
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b0763949ee

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aa286d0043

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e6bce50f69

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: adf48b9f24

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Remove aud, kid, assertion_jws from site.ext.vai assertions.
Use jws field (renamed from assertion_jws) in validation check.
Aligns integration example with current VAI payload structure.
@github-actions
Copy link

Tread carefully! This PR adds 30 linter errors (possibly disabled through directives):

  • modules/paywallsAnalyticsAdapter.js (+18 errors)
  • modules/paywallsRtdProvider.js (+12 errors)

@coveralls
Copy link
Collaborator

coveralls commented Mar 10, 2026

Pull Request Test Coverage Report for Build 23226456246

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 451 of 455 (99.12%) changed or added relevant lines in 4 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.006%) to 96.343%

Changes Missing Coverage Covered Lines Changed/Added Lines %
modules/paywallsAnalyticsAdapter.js 62 66 93.94%
Totals Coverage Status
Change from base Build 22969985052: 0.006%
Covered Lines: 218321
Relevant Lines: 226609

💛 - Coveralls

@ChrisHuie ChrisHuie self-requested a review March 10, 2026 17:46
paywalls-mike and others added 2 commits March 10, 2026 15:07
… site.ext.vai.jws reference

- Rename params.waitForIt to params.timeout to avoid collision with RTD framework's Boolean waitForIt (reviewer feedback)
- Fix integration example: site.ext.vai only has {iss, dom}, jws lives on user.ext.vai
- Update docs and tests to match
Copy link
Collaborator

@patmmccann patmmccann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please remove the analytics js load. Also, i am not totally following the polling for the window object to be there. Why not just advise pubs to get it set up in parallel with prebid and only inject as a last resort?

Remove loadExternalScript-based vai.js injection from both modules.
Publishers now load vai.js via a standard script tag before Prebid
initializes. Both modules read window.__PW_VAI__ directly.

RTD Provider:
- Remove loadExternalScript, getStorageManager, polling/hook/timeout
- getBidRequestData reads window.__PW_VAI__ synchronously
- Place VAI data under site.ext.data.vai / user.ext.data.vai (FPD convention)
- imp[].ext.vai unchanged (not first-party data)
- 22 tests rewritten and passing

Analytics Adapter:
- Remove loadExternalScript, ensureVai(), scriptUrl config
- getVaiClassification reads window.__PW_VAI__ directly
- 29 tests rewritten and passing

Integration Examples:
- Both example pages load vai.js via script tag
- Support ?real (production), ?degrade (no vai.js), default (mock)
- Remove scriptUrl/waitForIt/auctionDelay from configs
…AI signals

Update documentation to reflect the correct ORTB2 data structure:
- site.ext.vai -> site.ext.data.vai
- user.ext.vai -> user.ext.data.vai
- imp[].ext.vai unchanged (imp-level extensions are correct as-is)

Per ORTB2 convention, arbitrary/custom values belong in ext.data
rather than directly in ext. The code already uses ext.data; this
aligns the docs.
@github-actions
Copy link

Whoa there partner! This project is migrating to typescript. Consider changing the new JS files to TS, with well-defined types for what interacts with the prebid public API (for example: bid params and configuration). Thanks!

  • modules/paywallsAnalyticsAdapter.js
  • modules/paywallsRtdProvider.js

The paywalls RTD provider and analytics adapter no longer call loadExternalScript, so remove both entries from approvedLoadExternalScriptPaths.
@github-actions
Copy link

Whoa there partner! This project is migrating to typescript. Consider changing the new JS files to TS, with well-defined types for what interacts with the prebid public API (for example: bid params and configuration). Thanks!

  • modules/paywallsAnalyticsAdapter.js
  • modules/paywallsRtdProvider.js

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants