Skip to content

Conversation

@zeeshan-vwo
Copy link

What are the changes introduced in this PR?

This PR introduces a new VWO destination integration for audience sync for targetting. The implementation follows the modern TypeScript-first approach with Zod schemas for runtime validation.

What is the related Linear task?

N/A

Please explain the objectives of your changes below

Overview

VWO is an A/B testing and conversion optimization platform. This integration enables syncing of audience/cohorts from RudderStack to VWO.

Key Features

  • Audience Targeting: Seamlessly target VWO experiments and personalization experiences with RudderStack audience.
  • Multi-Region Support: Support for DEFAULT (US), EU, and AS regions

Files Added

Source Code:

  • src/v0/destinations/vwo/transform.ts - Main transformation logic with processor and router functions
  • src/v0/destinations/vwo/types.ts - TypeScript type definitions and Zod schemas
  • src/v0/destinations/vwo/utils.ts - Utility functions for payload building and validation
  • src/v0/destinations/vwo/config.ts - API endpoints and configuration constants

Tests:

  • test/integrations/destinations/vwo/processor/data.ts - 7 processor test cases
  • test/integrations/destinations/vwo/router/data.ts - 2 router test cases

Modified:

  • test/integrations/component.test.ts - Added 'vwo' to INTEGRATIONS_WITH_UPDATED_TEST_STRUCTURE

Any changes to existing capabilities/behaviour, mention the reason & what are the changes ?

N/A - This is a new destination integration with no impact on existing destinations.

Any new dependencies introduced with this change?

N/A - Uses existing dependencies (Zod is already in package.json)

Any new generic utility introduced or modified. Please explain the changes.

N/A - Uses existing utility functions from src/v0/util:

  • defaultRequestConfig()
  • defaultPostRequestConfig
  • simpleProcessRouterDest()
  • removeUndefinedAndNullValues()

Any technical or performance related pointers to consider with the change?

N/A

Test Coverage

  • ✅ Valid track events for all regions (DEFAULT, EU, AS)
  • ✅ Error handling for missing event name
  • ✅ Error handling for missing user identifier (userId/anonymousId)
  • ✅ Error handling for unsupported event types (identify, page, etc.)
  • ✅ Error handling for missing account ID in configuration
  • ✅ Batch processing with router pattern
  • ✅ Mixed valid/invalid events handling

@contributor-support
Copy link

Thank you @zeeshan-vwo for contributing this PR.
Please sign the Contributor License Agreement (CLA) before merging.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 5, 2025

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • test/integrations/component.test.ts is excluded by !**/test/**

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'auto_resolve_threads'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Summary by CodeRabbit

  • New Features
    • Added support for VWO destination, enabling users to send track events to VWO with multi-region endpoint selection (DEFAULT, EU, AS) and accountId-based configuration.

Walkthrough

Adds a new VWO destination: constant configuration, zod-based config schema and types, payload/endpoint utilities and validators, and transform logic for single and batched track events producing region-aware POST requests to VWO endpoints.

Changes

Cohort / File(s) Summary
Configuration
src/v0/destinations/vwo/config.ts
Adds BASE_URL, region-specific API_ENDPOINTS (DEFAULT, EU, AS), DESTINATION_NAME, and readonly SUPPORTED_EVENT_TYPES (['track']). Exports constants.
Types & Validation
src/v0/destinations/vwo/types.ts
Adds VWODestinationConfigSchema (zod) with required accountId and optional region enum (DEFAULT
Utilities
src/v0/destinations/vwo/utils.ts
Adds buildVWOPayload, validateVWOEvent, getUserIdentifier, and buildEndpoint. Implements payload sanitization and throws InstrumentationError when validation fails. Exports all helpers.
Transform
src/v0/destinations/vwo/transform.ts
Implements process(event) for single-event transformation and processRouterDest(inputs, reqMetadata) for batched routing. Validates message/config, resolves user identifier, builds payload, selects region endpoint, encodes endpoint URL, and returns standardized POST request objects with headers and JSON body.

Sequence Diagram(s)

sequenceDiagram
    participant Router
    participant Transform as transform.ts
    participant Utils as utils.ts
    participant Config as config.ts
    participant VWO as VWO API

    Router->>Transform: process(VWORouterRequest)
    Transform->>Utils: validateVWOEvent(message)
    alt valid
        Transform->>Utils: getUserIdentifier(message)
        Utils-->>Transform: userId
        Transform->>Utils: buildVWOPayload(message, userId, sessionId)
        Utils-->>Transform: VWOEventPayload
        Transform->>Config: read region / API_ENDPOINTS
        Config-->>Transform: selected base endpoint
        Transform->>Utils: buildEndpoint(baseEndpoint, accountId, eventName)
        Utils-->>Transform: encoded endpoint URL
        Transform-->>Router: POST request (headers + JSON body)
        Router->>VWO: POST request to endpoint
    else invalid
        Transform-->>Router: error (InstrumentationError / ConfigurationError)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Pay attention to validation/error types in utils.ts (InstrumentationError vs ConfigurationError).
  • Verify payload field mappings and removal of undefined/null values.
  • Confirm region-to-endpoint mapping and URL encoding in buildEndpoint.
  • Ensure processRouterDest batching conforms to router utilities and expected request shape/headers.

Suggested reviewers

  • krishna2020
  • maheshkutty
  • koladilip
  • sivashanmukh

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description comprehensively covers changes, objectives, test coverage, and follows the required template structure with all key sections populated.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title 'feat: add VWO destination integration' directly and clearly summarizes the main change—adding a new VWO destination integration to the codebase.

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/v0/destinations/vwo/transform.ts (1)

80-80: Simplify region handling with validated schema.

The region fallback logic here is redundant because the Zod schema in types.ts already provides a default value of 'DEFAULT' and validates the enum. The region field will always be a valid value ('DEFAULT', 'EU', or 'AS') at runtime.

Apply this diff to simplify:

-  const apiEndpoint = API_ENDPOINTS[region as keyof typeof API_ENDPOINTS] || API_ENDPOINTS.DEFAULT;
+  const apiEndpoint = API_ENDPOINTS[region];
src/v0/destinations/vwo/utils.ts (1)

71-73: Consider tightening the return type.

The function returns an empty string if both userId and anonymousId are missing, but validateVWOEvent ensures at least one exists. The empty string case should never occur in practice.

For stricter type safety, you could:

  1. Add an assertion: return message.userId || message.anonymousId || throw new Error('Unreachable')
  2. Or document that this function assumes validation has already occurred
  3. Or use a non-nullable return type if the TypeScript configuration supports it

This is a minor concern since validateVWOEvent is called before this function in the actual flow.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c44292f and 3c6440e.

⛔ Files ignored due to path filters (3)
  • test/integrations/component.test.ts is excluded by !**/test/**
  • test/integrations/destinations/vwo/processor/data.ts is excluded by !**/test/**
  • test/integrations/destinations/vwo/router/data.ts is excluded by !**/test/**
📒 Files selected for processing (4)
  • src/v0/destinations/vwo/config.ts (1 hunks)
  • src/v0/destinations/vwo/transform.ts (1 hunks)
  • src/v0/destinations/vwo/types.ts (1 hunks)
  • src/v0/destinations/vwo/utils.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: ItsSudip
Repo: rudderlabs/rudder-transformer PR: 4497
File: src/v0/destinations/tiktok_ads/transformV2.js:686-691
Timestamp: 2025-07-15T06:27:07.528Z
Learning: For TikTok Ads destination in src/v0/destinations/tiktok_ads/transformV2.js, there are plans for a future enhancement to group events by eventSource when batching to ensure all events in a batch have the same event_source value, as acknowledged by ItsSudip in PR #4497.
📚 Learning: 2025-07-15T06:27:07.528Z
Learnt from: ItsSudip
Repo: rudderlabs/rudder-transformer PR: 4497
File: src/v0/destinations/tiktok_ads/transformV2.js:686-691
Timestamp: 2025-07-15T06:27:07.528Z
Learning: For TikTok Ads destination in src/v0/destinations/tiktok_ads/transformV2.js, there are plans for a future enhancement to group events by eventSource when batching to ensure all events in a batch have the same event_source value, as acknowledged by ItsSudip in PR #4497.

Applied to files:

  • src/v0/destinations/vwo/transform.ts
  • src/v0/destinations/vwo/types.ts
📚 Learning: 2025-05-29T13:29:39.436Z
Learnt from: maheshkutty
Repo: rudderlabs/rudder-transformer PR: 4359
File: src/v0/util/index.js:2272-2272
Timestamp: 2025-05-29T13:29:39.436Z
Learning: The `combineBatchRequestsWithSameJobIds` function in `src/v0/util/index.js` is used by both Mixpanel and Google AdWords Offline Conversions destinations in production. The function assumes metadata is always an array in multiple operations (forEach, filter, sort, map) and needs defensive programming to handle non-array metadata cases to prevent runtime errors.

Applied to files:

  • src/v0/destinations/vwo/transform.ts
🧬 Code graph analysis (3)
src/v0/destinations/vwo/transform.ts (5)
src/v0/destinations/vwo/types.ts (1)
  • VWORouterRequest (45-45)
src/v0/destinations/vwo/utils.ts (4)
  • validateVWOEvent (52-62)
  • getUserIdentifier (71-73)
  • buildVWOPayload (13-44)
  • buildEndpoint (83-89)
src/v0/destinations/vwo/config.ts (1)
  • API_ENDPOINTS (21-21)
src/v0/util/index.js (3)
  • defaultRequestConfig (461-475)
  • defaultPostRequestConfig (435-438)
  • simpleProcessRouterDest (1815-1834)
src/v0/util/constant.js (1)
  • JSON_MIME_TYPE (11-11)
src/v0/destinations/vwo/utils.ts (3)
src/types/rudderEvents.ts (1)
  • RudderMessage (43-43)
src/v0/destinations/vwo/types.ts (1)
  • VWOEventPayload (21-38)
src/v0/util/index.js (1)
  • removeUndefinedAndNullValues (55-55)
src/v0/destinations/vwo/types.ts (3)
src/types/controlPlaneConfig.ts (1)
  • Destination (31-49)
src/types/destinationTransformation.ts (1)
  • RouterTransformationRequestData (95-106)
src/types/rudderEvents.ts (1)
  • RudderMessage (43-43)
🔇 Additional comments (6)
src/v0/destinations/vwo/types.ts (2)

9-14: LGTM! Well-structured validation schema.

The Zod schema provides strong runtime validation with appropriate constraints:

  • Required accountId with non-empty validation
  • Region enum with sensible default
  • Passthrough for config extensibility

21-38: LGTM! Clear payload structure definition.

The interface clearly defines the VWO event payload structure with proper nesting and the flexible [key: string]: any index signature to accommodate custom event properties.

src/v0/destinations/vwo/utils.ts (3)

13-44: LGTM! Payload construction follows best practices.

The function correctly:

  • Generates unique message IDs and session identifiers
  • Safely extracts event properties with fallback
  • Protects critical metadata fields by spreading eventProperties first, then explicitly setting eventType and eventName to prevent user overrides
  • Sanitizes the payload with removeUndefinedAndNullValues

52-62: LGTM! Clear validation with appropriate error messages.

The validation logic correctly enforces:

  • Event name requirement for track events
  • User identifier requirement (userId or anonymousId)

Error messages are clear and actionable.


83-89: LGTM! Proper URL encoding for query parameters.

The function correctly uses encodeURIComponent for both eventName and accountId to prevent injection issues and handle special characters.

src/v0/destinations/vwo/config.ts (1)

7-7: The BASE_URL is correct for production use—no changes needed.

The official VWO production API endpoint for offline conversion events is indeed https://dev.visualwebsiteoptimizer.com. The "dev" prefix in the domain is part of VWO's official production URL and should not be modified. The code is correct as-is.

@zeeshan-vwo
Copy link
Author

zeeshan-vwo commented Nov 7, 2025

@maheshkutty @krishna2020 can you please review this PR. Please let me know if I have to make any other changes.
Thank you

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/v0/destinations/vwo/utils.ts (1)

71-79: Consider a more descriptive error message for the unreachable case.

While line 78 should be unreachable due to prior validation in validateVWOEvent, using a more descriptive error message would aid debugging if this invariant is ever violated during refactoring or changes to the call flow.

Apply this diff to improve the error message:

   if (message.anonymousId) {
     return message.anonymousId;
   }
-  throw new Error('Unreachable');
+  throw new InstrumentationError(
+    'Neither userId nor anonymousId is available. This should have been caught by validateVWOEvent.'
+  );
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 047e999 and ccecf3a.

⛔ Files ignored due to path filters (1)
  • test/integrations/component.test.ts is excluded by !**/test/**
📒 Files selected for processing (2)
  • src/v0/destinations/vwo/transform.ts (1 hunks)
  • src/v0/destinations/vwo/utils.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: maheshkutty
Repo: rudderlabs/rudder-transformer PR: 4380
File: src/v0/destinations/google_adwords_remarketing_lists/config.js:3-5
Timestamp: 2025-06-04T07:24:58.591Z
Learning: User maheshkutty prefers to handle Google Ads API version upgrades for different destinations in separate pull requests rather than bundling multiple destinations together in a single PR.
Learnt from: ItsSudip
Repo: rudderlabs/rudder-transformer PR: 4497
File: src/v0/destinations/tiktok_ads/transformV2.js:686-691
Timestamp: 2025-07-15T06:27:07.528Z
Learning: For TikTok Ads destination in src/v0/destinations/tiktok_ads/transformV2.js, there are plans for a future enhancement to group events by eventSource when batching to ensure all events in a batch have the same event_source value, as acknowledged by ItsSudip in PR #4497.
📚 Learning: 2025-07-15T06:27:07.528Z
Learnt from: ItsSudip
Repo: rudderlabs/rudder-transformer PR: 4497
File: src/v0/destinations/tiktok_ads/transformV2.js:686-691
Timestamp: 2025-07-15T06:27:07.528Z
Learning: For TikTok Ads destination in src/v0/destinations/tiktok_ads/transformV2.js, there are plans for a future enhancement to group events by eventSource when batching to ensure all events in a batch have the same event_source value, as acknowledged by ItsSudip in PR #4497.

Applied to files:

  • src/v0/destinations/vwo/transform.ts
📚 Learning: 2025-05-29T13:29:39.436Z
Learnt from: maheshkutty
Repo: rudderlabs/rudder-transformer PR: 4359
File: src/v0/util/index.js:2272-2272
Timestamp: 2025-05-29T13:29:39.436Z
Learning: The `combineBatchRequestsWithSameJobIds` function in `src/v0/util/index.js` is used by both Mixpanel and Google AdWords Offline Conversions destinations in production. The function assumes metadata is always an array in multiple operations (forEach, filter, sort, map) and needs defensive programming to handle non-array metadata cases to prevent runtime errors.

Applied to files:

  • src/v0/destinations/vwo/transform.ts
🧬 Code graph analysis (2)
src/v0/destinations/vwo/transform.ts (5)
src/v0/destinations/vwo/types.ts (1)
  • VWORouterRequest (45-45)
src/v0/destinations/vwo/utils.ts (4)
  • validateVWOEvent (52-62)
  • getUserIdentifier (71-79)
  • buildVWOPayload (13-44)
  • buildEndpoint (89-95)
src/v0/destinations/vwo/config.ts (1)
  • API_ENDPOINTS (21-21)
src/v0/util/index.js (3)
  • defaultRequestConfig (461-475)
  • defaultPostRequestConfig (435-438)
  • simpleProcessRouterDest (1815-1834)
src/v0/util/constant.js (1)
  • JSON_MIME_TYPE (11-11)
src/v0/destinations/vwo/utils.ts (3)
src/types/rudderEvents.ts (1)
  • RudderMessage (43-43)
src/v0/destinations/vwo/types.ts (1)
  • VWOEventPayload (21-38)
src/v0/util/index.js (1)
  • removeUndefinedAndNullValues (55-55)
🔇 Additional comments (6)
src/v0/destinations/vwo/transform.ts (3)

1-35: LGTM! Clear documentation and well-organized imports.

The file header provides excellent context about VWO integration features and supported operations. The imports are properly structured and include all necessary dependencies.


110-112: LGTM! Clean wrapper function.

The process function provides a clean public API by delegating to processEvent. This separation is appropriate for the transformer pattern.


124-130: LGTM! Proper batch processing implementation.

The processRouterDest function correctly uses the simpleProcessRouterDest utility for batch event processing. The empty processParams object is appropriate since no additional processing parameters are needed for VWO.

src/v0/destinations/vwo/utils.ts (3)

1-4: LGTM! Appropriate imports.

All imports are necessary for the utility functions and properly typed.


52-62: LGTM! Comprehensive validation logic.

The validation function properly checks required fields for track events and ensures either userId or anonymousId is present. Error messages are clear and actionable.


89-95: LGTM! Proper query parameter encoding.

The function correctly uses encodeURIComponent for both the event name and account ID query parameters, preventing potential URL injection issues. Implementation is clean and secure.

@zeeshan-vwo zeeshan-vwo changed the title feat(vwo): add VWO destination integration feat: add VWO destination integration Nov 11, 2025
@zeeshan-vwo
Copy link
Author

@krishna2020 @maheshkutty, can you please review this? Our customers are blocked because of this. We will be very grateful if you can review it as soon as possible.

@Drewdodds
Copy link

@maheshkutty @krishna2020 can you please review this PR. Please let me know if I have to make any other changes. Thank you

Hey, @zeeshan-vwo , I work on the product team at rudderstack. Would you be open to a quick 30-minute call to walk through the integration and answer questions? This would really accelerate our understanding and review process.

On our end, we're setting up a VWO test account to explore the platform (would love if this could be free with no trial period so we can continue to test)
We are also researching VWO's API capabilities and documentation. If you could send any relevant docs for us, that would be great.
I am preparing a Product Requirements Document (PRD) for our engineering team to review along with this PR.

We're aiming to fast-track integrations that provide clear customer value, so your insights will be incredibly helpful in moving this forward efficiently.

@zeeshan-vwo
Copy link
Author

@Drewdodds Sure, we can have a quick call over this. Please feel free to block any of the suitable time from the link below.
https://calendar.app.google/NoJfrCXDezEQZfhA6

@Drewdodds
Copy link

Drewdodds commented Dec 9, 2025

@Drewdodds Sure, we can have a quick call over this. Please feel free to block any of the suitable time from the link below. https://calendar.app.google/NoJfrCXDezEQZfhA6

Schedule for Friday at 9:00 EST. Look forward to talking. I will add an engineer from my side as well. Can you [email protected] to the invite?

@zeeshan-vwo
Copy link
Author

@Drewdodds Sure, I’ve added [email protected] to the invite, but I saw a warning while adding it—could you please confirm if the invite went through?

@devops-github-rudderstack
Copy link
Contributor

This PR is considered to be stale. It has been open for 20 days with no further activity thus it is going to be closed in 7 days. To avoid such a case please consider removing the stale label manually or add a comment to the PR.

@Drewdodds
Copy link

This PR is considered to be stale. It has been open for 20 days with no further activity thus it is going to be closed in 7 days. To avoid such a case please consider removing the stale label manually or add a comment to the PR.

We are still working on this integration. This PR may not be merged but we are working on a PR that will solve for the use cases here as well as others.

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.

4 participants