Skip to content

Add Kochava (Actions) cloud destination - #3939

Open
mdkhan-tw wants to merge 2 commits into
mainfrom
actions-kochava-destination
Open

Add Kochava (Actions) cloud destination#3939
mdkhan-tw wants to merge 2 commits into
mainfrom
actions-kochava-destination

Conversation

@mdkhan-tw

Copy link
Copy Markdown
Contributor

Summary

Adds a new cloud-mode destination for Kochava's Server-to-Server integration, forwarding Segment Track events for mobile attribution and analytics.

Two actions, both POST https://control.kochava.com/track/json, multiplexed by the top-level action field:

Action Default Subscription action value
Post-Install Event (event) type = "track" and event != "Application Installed" event
Install Notification (install) type = "track" and event = "Application Installed" install

Design notes

  • Authentication: single required setting kochava_app_id (the App GUID). Kochava's S2S API exposes no auth-verification endpoint and uses no token/OAuth, so there is no testAuthentication (matches the existing Adjust destination pattern).
  • Device identifiers: modeled as idfa / idfv / adid / android_id fields, assembled into Kochava's device_ids object. Runtime validation requires at least one (PayloadValidationError).
  • Transforms: device_ua URL-encoded, usertime sent as epoch seconds, device_limit_tracking derived as the inverse of context.device.adTrackingEnabled.
  • iOS 14+: optional App Tracking Transparency block (att, att_time, att_duration, att_detail) plus ad_services_token (install).
  • No performBatch: /track/json accepts a single record per call — no batch/bulk endpoint is documented.
  • File organization: shared constants.ts / types.ts / utils.ts / fields.ts; each action perform is a thin wrapper delegating to utils.sendEvent. All request bodies and responses are typed.

Testing

  • 7 unit tests (happy path + App ID override + validation edge cases) across both actions
  • 4 snapshot tests (required + all fields, both actions) — reviewed
  • 3 destination-level metadata/preset tests
  • 14/14 passing; yarn types, typecheck, and lint all clean
cd packages/destination-actions && TZ=UTC ./node_modules/.bin/jest --testPathPattern="kochava"

⚠️ TODO before merge

Register this destination in packages/destination-actions/src/destinations/index.ts with the production-assigned metadata ID once it's created in Segment's production control plane. The ID is a real MongoDB ObjectId that must match across environments (synced via sprout) — intentionally not invented here.

Open questions (from spec)

  • Kochava /track/json rate limits (undocumented)
  • Confirm usertime is epoch seconds (assumed from sample)
  • Confirm device_limit_tracking polarity (inverse of adTrackingEnabled)

🤖 Generated with Claude Code

Adds a new cloud-mode destination for Kochava's Server-to-Server
integration with two actions:

- event (Post-Install Event): sends in-app events to
  POST /track/json with action="event"
- install (Install Notification): sends install notifications to
  POST /track/json with action="install"

Both actions share a common field set and payload builder
(constants.ts / types.ts / utils.ts / fields.ts). Highlights:

- Auth: single required App GUID setting (kochava_app_id); Kochava
  exposes no auth-verification endpoint, so no testAuthentication
  (matches the Adjust destination pattern).
- Device identifiers modeled as idfa/idfv/adid/android_id, assembled
  into device_ids; at least one is required (PayloadValidationError).
- Transforms: device_ua URL-encoded, usertime as epoch seconds,
  device_limit_tracking as the inverse of context.device.adTrackingEnabled.
- No performBatch: /track/json accepts a single record per call (no
  batch endpoint documented).

Tests: 7 unit + 4 snapshot + 3 destination-level (all passing);
typecheck and lint clean.

TODO before merge: register in destinations/index.ts with the
production-assigned metadata ID once the destination is created in
Segment production (ID must match across environments).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mdkhan-tw
mdkhan-tw requested a review from a team as a code owner August 11, 2026 15:40
Copilot AI lite review requested due to automatic review settings August 11, 2026 15:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Adds a new cloud-mode Kochava destination that forwards Segment Track events to Kochava’s /track/json endpoint, supporting both install notifications and post-install events.

Changes:

  • Introduces Kochava destination definition with two actions (install, event) and shared field definitions.
  • Adds request-building utilities and types for constructing typed /track/json payloads (including ATT + AdServices token support).
  • Adds unit tests, snapshot tests, and destination metadata/preset tests for the new destination.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/destination-actions/src/destinations/kochava/index.ts Defines the new Kochava cloud destination, presets, and action wiring.
packages/destination-actions/src/destinations/kochava/utils.ts Builds/validates Kochava request bodies and sends requests to /track/json.
packages/destination-actions/src/destinations/kochava/types.ts Adds typed request/response and shared payload shapes for both actions.
packages/destination-actions/src/destinations/kochava/fields.ts Defines shared action fields (device ids, usertime, ATT, etc.).
packages/destination-actions/src/destinations/kochava/constants.ts Adds endpoint constants and action discriminators.
packages/destination-actions/src/destinations/kochava/metadata.json Declares destination + action metadata, fields, and presets.
packages/destination-actions/src/destinations/kochava/**/tests/* Adds unit tests and snapshot tests for the destination and actions.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +92 to +95
const deviceIds = buildDeviceIds(payload)
if (Object.keys(deviceIds).length === 0) {
throw new PayloadValidationError('At least one device identifier (IDFA, IDFV, ADID, or Android ID) is required.')
}
Comment on lines +19 to +24
function toEpochSeconds(value?: string | number): number | undefined {
if (value === undefined || value === null || value === '') return undefined
const ms = new Date(value).getTime()
if (Number.isNaN(ms)) return undefined
return Math.floor(ms / 1000)
}
Comment on lines +3 to +9
export interface KochavaDeviceIds {
idfa?: string
idfv?: string
adid?: string
android_id?: string
[key: string]: string | undefined
}
Comment on lines +46 to +56
device_ua: {
label: 'Device User Agent',
description: 'The device user agent string. Either this or Device OS Version is required for OS detection.',
type: 'string',
required: false,
default: { '@path': '$.context.userAgent' }
},
device_ver: {
label: 'Device OS Version',
description: 'The device OS version. Either this or Device User Agent is required for OS detection.',
type: 'string',
Comment on lines +16 to +18
nock(/.*/).persist().get(/.*/).reply(200)
nock(/.*/).persist().post(/.*/).reply(200)
nock(/.*/).persist().put(/.*/).reply(200)
Adds __tests__/e2e.test.ts covering both actions against a local
serve server (./bin/run serve --destination=kochava --noUI):

- authentication (valid settings)
- event + install delivery via perform() (real HTTP to Kochava)
- device-identifier and required-field validation paths
- skips for paths not reachable over HTTP (no testAuthentication,
  no performBatch, live-dependent HTTP error mapping)

Uses Node's http module against 127.0.0.1 (Jest's node environment
does not expose global fetch, and serve binds IPv4 only). Not run in
CI — requires a running serve server.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 12, 2026 07:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

packages/destination-actions/src/destinations/kochava/utils.ts:42

  • The current non-empty check will accept whitespace-only identifiers (e.g., ' '), which are effectively empty but will pass validation and be sent downstream. Consider trimming string inputs before the length check (and before assignment) so whitespace-only values are excluded.
  for (const [key, val] of candidates) {
    if (val !== undefined && val !== null && String(val).length > 0) {
      result[key] = String(val)
    }
  }

packages/destination-actions/src/destinations/kochava/install/tests/index.test.ts:72

  • This assertion depends on undefined properties being present in the parsed JSON object. Standard JSON serialization omits keys with undefined values, so this test can become brittle across request-client/serialization changes. Prefer asserting only on the fields that must exist (e.g., toMatchObject({ att: true, att_time: 1616263616 })) and optionally asserting absent keys with not.toHaveProperty(...).
    expect(body.data.app_tracking_transparency).toEqual({
      att: true,
      att_time: 1616263616,
      att_duration: undefined,
      att_detail: undefined
    })

packages/destination-actions/src/destinations/kochava/tests/snapshot.test.ts:18

  • Using nock(...).persist() without a matching cleanup (e.g., nock.cleanAll() / nock.restore() in afterEach/afterAll) can leak interceptors across tests and cause order-dependent failures in the broader test suite. Please add teardown for nock in this file or avoid persist() if it isn't necessary.
      nock(/.*/).persist().get(/.*/).reply(200)
      nock(/.*/).persist().post(/.*/).reply(200)
      nock(/.*/).persist().put(/.*/).reply(200)

packages/destination-actions/src/destinations/kochava/tests/e2e.test.ts:27

  • This e2e suite is documented as out-of-CI but still runs by default when directly targeted, and it can result in real outbound calls to Kochava via the local serve process. To reduce accidental external calls, consider gating the entire suite behind an explicit env flag (e.g., if (!process.env.RUN_KOCHAVA_E2E) describe.skip(...)) and/or requiring KOCHAVA_APP_ID to be explicitly set (no dummy default).
const BASE_URL = process.env.BASE_URL ?? 'http://127.0.0.1:3000'
const KOCHAVA_APP_ID = process.env.KOCHAVA_APP_ID ?? 'e2e-dummy-app-guid'

jest.setTimeout(30000)

Comment on lines +19 to +24
function toEpochSeconds(value?: string | number): number | undefined {
if (value === undefined || value === null || value === '') return undefined
const ms = new Date(value).getTime()
if (Number.isNaN(ms)) return undefined
return Math.floor(ms / 1000)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants