From 82eabd0eac7138e5a4824f43055ba8eecb87221c Mon Sep 17 00:00:00 2001 From: Ankit Gupta Date: Tue, 5 May 2026 18:55:27 +0530 Subject: [PATCH 1/5] Added validation on external_id of character 255 --- .../src/destinations/klaviyo/config.ts | 1 + .../src/destinations/klaviyo/functions.ts | 45 ++++++++++++++++++- .../klaviyo/orderCompleted/index.ts | 3 +- .../klaviyo/removeProfile/index.ts | 2 + .../klaviyo/removeProfileFromList/index.ts | 9 +++- .../destinations/klaviyo/trackEvent/index.ts | 3 +- .../klaviyo/upsertProfile/index.ts | 3 ++ 7 files changed, 62 insertions(+), 4 deletions(-) diff --git a/packages/destination-actions/src/destinations/klaviyo/config.ts b/packages/destination-actions/src/destinations/klaviyo/config.ts index a41f7ffb36c..925f84a8e05 100644 --- a/packages/destination-actions/src/destinations/klaviyo/config.ts +++ b/packages/destination-actions/src/destinations/klaviyo/config.ts @@ -2,6 +2,7 @@ import { KLAVIYO_REVISION_DATE } from './versioning-info' export const API_URL = 'https://a.klaviyo.com/api' export const REVISION_DATE = KLAVIYO_REVISION_DATE +export const MAX_EXTERNAL_ID_LENGTH = 255 export const COUNTRY_CODES = [ { label: 'AD - Andorra', value: 'AD' }, { label: 'AE - United Arab Emirates', value: 'AE' }, diff --git a/packages/destination-actions/src/destinations/klaviyo/functions.ts b/packages/destination-actions/src/destinations/klaviyo/functions.ts index bb0efcaf0c9..13303d69580 100644 --- a/packages/destination-actions/src/destinations/klaviyo/functions.ts +++ b/packages/destination-actions/src/destinations/klaviyo/functions.ts @@ -10,7 +10,7 @@ import { ErrorCodes, StatsContext } from '@segment/actions-core' -import { API_URL, REVISION_DATE } from './config' +import { API_URL, REVISION_DATE, MAX_EXTERNAL_ID_LENGTH } from './config' import { Settings } from './generated-types' import { KlaviyoAPIError, @@ -40,6 +40,12 @@ import { ActionDestinationErrorResponseType } from '@segment/actions-core/destin const phoneUtil = PhoneNumberUtil.getInstance() +export function validateExternalId(externalId: string | undefined): void { + if (externalId && externalId.length > MAX_EXTERNAL_ID_LENGTH) { + throw new PayloadValidationError(`Length of external_id must be no more than ${MAX_EXTERNAL_ID_LENGTH} characters.`) + } +} + export async function getListIdDynamicData(request: RequestClient): Promise { try { const result: ListIdResponse = await request(`${API_URL}/lists/`, { @@ -100,6 +106,7 @@ export async function createProfile( additionalAttributes: AdditionalAttributes ) { try { + validateExternalId(external_id) const profileData: ProfileData = { data: { type: 'profile', @@ -586,6 +593,15 @@ function validateAndConstructRemoveProfilePayloads(payload: RemoveProfilePayload return response } + if (external_id && external_id.length > MAX_EXTERNAL_ID_LENGTH) { + response.error = { + status: 400, + errortype: 'PAYLOAD_VALIDATION_FAILED', + errormessage: `Length of external_id must be no more than ${MAX_EXTERNAL_ID_LENGTH} characters.` + } + return response + } + if (phone_number) { const validPhoneNumber = validateAndConvertPhoneNumber(phone_number, payload.country_code as string) if (!validPhoneNumber) { @@ -616,6 +632,15 @@ function validateAndConstructProfilePayload(payload: AddProfileToListPayload): { return response } + if (external_id && external_id.length > MAX_EXTERNAL_ID_LENGTH) { + response.error = { + status: 400, + errortype: 'PAYLOAD_VALIDATION_FAILED', + errormessage: `Length of external_id must be no more than ${MAX_EXTERNAL_ID_LENGTH} characters.` + } + return response + } + if (phone_number) { const validPhoneNumber = validateAndConvertPhoneNumber(phone_number, payload.country_code as string) if (!validPhoneNumber) { @@ -774,6 +799,15 @@ function validateAndPreparePayloads(payloads: TrackEventPayload[], multiStatusRe delete payload?.profile?.country_code } + if (external_id && external_id.length > MAX_EXTERNAL_ID_LENGTH) { + multiStatusResponse.setErrorResponseAtIndex(originalBatchIndex, { + status: 400, + errortype: 'PAYLOAD_VALIDATION_FAILED', + errormessage: `Length of external_id must be no more than ${MAX_EXTERNAL_ID_LENGTH} characters.` + }) + return + } + const profileToAdd = constructBulkCreateEventPayload(payload) filteredPayloads.push(profileToAdd as JSONLikeObject) validPayloadIndicesBitmap.push(originalBatchIndex) @@ -920,6 +954,15 @@ export function validateProfilePayload(payload: Payload): validateProfilePayload return response } + if (payload.external_id && payload.external_id.length > MAX_EXTERNAL_ID_LENGTH) { + response.error = { + status: 400, + errortype: 'PAYLOAD_VALIDATION_FAILED', + errormessage: `Length of external_id must be no more than ${MAX_EXTERNAL_ID_LENGTH} characters.` + } + return response + } + if (payload.phone_number) { const validPhoneNumber = validateAndConvertPhoneNumber(payload.phone_number, payload.country_code as string) if (!validPhoneNumber) { diff --git a/packages/destination-actions/src/destinations/klaviyo/orderCompleted/index.ts b/packages/destination-actions/src/destinations/klaviyo/orderCompleted/index.ts index 3417988c41b..49d07a974ea 100644 --- a/packages/destination-actions/src/destinations/klaviyo/orderCompleted/index.ts +++ b/packages/destination-actions/src/destinations/klaviyo/orderCompleted/index.ts @@ -5,7 +5,7 @@ import { PayloadValidationError, RequestClient } from '@segment/actions-core' import { API_URL } from '../config' import { EventData } from '../types' import { v4 as uuidv4 } from '@lukeed/uuid' -import { processPhoneNumber } from '../functions' +import { processPhoneNumber, validateExternalId } from '../functions' import { country_code } from '../properties' import dayjs from 'dayjs' @@ -176,6 +176,7 @@ const action: ActionDefinition = { if (!email && !phone_number && !external_id && !anonymous_id) { throw new PayloadValidationError('One of External ID, Anonymous ID, Phone Number or Email is required.') } + validateExternalId(external_id) const eventData = createEventData(payload) diff --git a/packages/destination-actions/src/destinations/klaviyo/removeProfile/index.ts b/packages/destination-actions/src/destinations/klaviyo/removeProfile/index.ts index 7aa007b937a..0c9eef95723 100644 --- a/packages/destination-actions/src/destinations/klaviyo/removeProfile/index.ts +++ b/packages/destination-actions/src/destinations/klaviyo/removeProfile/index.ts @@ -6,6 +6,7 @@ import { getListIdDynamicData, getProfiles, processPhoneNumber, + validateExternalId, removeBulkProfilesFromList, removeProfileFromList } from '../functions' @@ -67,6 +68,7 @@ const action: ActionDefinition = { if (!email && !external_id && !phone_number) { throw new PayloadValidationError('One of External ID, Phone Number and Email is required.') } + validateExternalId(external_id) const profileIds = await getProfiles( request, email ? [email] : undefined, diff --git a/packages/destination-actions/src/destinations/klaviyo/removeProfileFromList/index.ts b/packages/destination-actions/src/destinations/klaviyo/removeProfileFromList/index.ts index be73107fa09..00dc73cd1d0 100644 --- a/packages/destination-actions/src/destinations/klaviyo/removeProfileFromList/index.ts +++ b/packages/destination-actions/src/destinations/klaviyo/removeProfileFromList/index.ts @@ -2,7 +2,13 @@ import { ActionDefinition, PayloadValidationError } from '@segment/actions-core' import type { Settings } from '../generated-types' import { Payload } from './generated-types' -import { getProfiles, processPhoneNumber, removeBulkProfilesFromList, removeProfileFromList } from '../functions' +import { + getProfiles, + processPhoneNumber, + validateExternalId, + removeBulkProfilesFromList, + removeProfileFromList +} from '../functions' import { email, list_id, external_id, enable_batching, phone_number, country_code, batch_size } from '../properties' const action: ActionDefinition = { @@ -33,6 +39,7 @@ const action: ActionDefinition = { if (!email && !external_id && !phone_number) { throw new PayloadValidationError('One of External ID, Phone Number and Email is required.') } + validateExternalId(external_id) const profileIds = await getProfiles( request, email ? [email] : undefined, diff --git a/packages/destination-actions/src/destinations/klaviyo/trackEvent/index.ts b/packages/destination-actions/src/destinations/klaviyo/trackEvent/index.ts index 1a8fc078057..3e3c0cb5b42 100644 --- a/packages/destination-actions/src/destinations/klaviyo/trackEvent/index.ts +++ b/packages/destination-actions/src/destinations/klaviyo/trackEvent/index.ts @@ -4,7 +4,7 @@ import type { Payload } from './generated-types' import { PayloadValidationError } from '@segment/actions-core' import { API_URL } from '../config' import { batch_size, enable_batching, country_code } from '../properties' -import { processPhoneNumber, sendBatchedTrackEvent } from '../functions' +import { processPhoneNumber, sendBatchedTrackEvent, validateExternalId } from '../functions' import dayjs from '../../../lib/dayjs' const action: ActionDefinition = { @@ -106,6 +106,7 @@ const action: ActionDefinition = { if (!email && !phone_number && !external_id && !anonymous_id) { throw new PayloadValidationError('One of External ID, Anonymous ID, Phone Number or Email is required.') } + validateExternalId(external_id) const eventData = { data: { type: 'event', diff --git a/packages/destination-actions/src/destinations/klaviyo/upsertProfile/index.ts b/packages/destination-actions/src/destinations/klaviyo/upsertProfile/index.ts index a82e0ab6c8a..31a18ebff0c 100644 --- a/packages/destination-actions/src/destinations/klaviyo/upsertProfile/index.ts +++ b/packages/destination-actions/src/destinations/klaviyo/upsertProfile/index.ts @@ -13,6 +13,7 @@ import { getList, createList, processPhoneNumber, + validateExternalId, validateProfilePayload, updateMultiStatusWithSuccessData, updateMultiStatusWithKlaviyoErrors @@ -260,6 +261,8 @@ const action: ActionDefinition = { throw new PayloadValidationError('One of External ID, Phone Number and Email is required.') } + validateExternalId(external_id) + const profileData: ProfileData = { data: { type: 'profile', From e869df27d410448a1b056eb089366e50c9bf9186 Mon Sep 17 00:00:00 2001 From: Ankit Gupta Date: Tue, 5 May 2026 18:58:49 +0530 Subject: [PATCH 2/5] Added description in external_id field --- .../destination-actions/src/destinations/klaviyo/properties.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/destination-actions/src/destinations/klaviyo/properties.ts b/packages/destination-actions/src/destinations/klaviyo/properties.ts index c0ae56e66b1..30c5ea69eba 100644 --- a/packages/destination-actions/src/destinations/klaviyo/properties.ts +++ b/packages/destination-actions/src/destinations/klaviyo/properties.ts @@ -30,7 +30,7 @@ export const email: InputField = { export const external_id: InputField = { label: 'External ID', - description: `A unique identifier used by customers to associate Klaviyo profiles with profiles in an external system. One of External ID and Email required.`, + description: `A unique identifier used by customers to associate Klaviyo profiles with profiles in an external system. One of External ID and Email required. The length should be between 1 and 255 characters.`, type: 'string' } From f462239c58d6e2bc2c452769394e119d138d19eb Mon Sep 17 00:00:00 2001 From: Ankit Gupta Date: Tue, 5 May 2026 20:19:42 +0530 Subject: [PATCH 3/5] Resolved the copilot issue --- .../klaviyo/__tests__/multistatus.test.ts | 42 +++++++++++++++++++ .../src/destinations/klaviyo/functions.ts | 34 ++++++--------- .../upsertProfile/__tests__/index.test.ts | 38 +++++++++++++++++ 3 files changed, 92 insertions(+), 22 deletions(-) diff --git a/packages/destination-actions/src/destinations/klaviyo/__tests__/multistatus.test.ts b/packages/destination-actions/src/destinations/klaviyo/__tests__/multistatus.test.ts index 340f90df9df..8c842b86961 100644 --- a/packages/destination-actions/src/destinations/klaviyo/__tests__/multistatus.test.ts +++ b/packages/destination-actions/src/destinations/klaviyo/__tests__/multistatus.test.ts @@ -257,6 +257,48 @@ describe('MultiStatus', () => { errorreporter: 'INTEGRATIONS' }) }) + + it('should reject events where external_id exceeds 255 characters', async () => { + nock(API_URL).post('/event-bulk-create-jobs/').reply(202, {}) + + const longExternalId = 'a'.repeat(256) + const events: SegmentEvent[] = [ + // Event with external_id exceeding 255 characters + createTestEvent({ + type: 'track', + timestamp, + properties: { + external_id: longExternalId + } + }), + // Valid Event + createTestEvent({ + type: 'track', + timestamp, + properties: { + email: 'valid@gmail.com' + } + }) + ] + + const response = await testDestination.executeBatch('trackEvent', { + events, + settings, + mapping + }) + + expect(response[0]).toMatchObject({ + status: 400, + errortype: 'PAYLOAD_VALIDATION_FAILED', + errormessage: 'Length of external_id must be no more than 255 characters.', + errorreporter: 'INTEGRATIONS' + }) + + expect(response[1]).toMatchObject({ + status: 200, + body: '{}' + }) + }) }) describe('addProfileToList', () => { beforeEach(() => { diff --git a/packages/destination-actions/src/destinations/klaviyo/functions.ts b/packages/destination-actions/src/destinations/klaviyo/functions.ts index 13303d69580..fedb40d4bbf 100644 --- a/packages/destination-actions/src/destinations/klaviyo/functions.ts +++ b/packages/destination-actions/src/destinations/klaviyo/functions.ts @@ -40,9 +40,15 @@ import { ActionDestinationErrorResponseType } from '@segment/actions-core/destin const phoneUtil = PhoneNumberUtil.getInstance() +const EXTERNAL_ID_LENGTH_ERROR: ActionDestinationErrorResponseType = { + status: 400, + errortype: 'PAYLOAD_VALIDATION_FAILED', + errormessage: `Length of external_id must be no more than ${MAX_EXTERNAL_ID_LENGTH} characters.` +} + export function validateExternalId(externalId: string | undefined): void { if (externalId && externalId.length > MAX_EXTERNAL_ID_LENGTH) { - throw new PayloadValidationError(`Length of external_id must be no more than ${MAX_EXTERNAL_ID_LENGTH} characters.`) + throw new PayloadValidationError(EXTERNAL_ID_LENGTH_ERROR.errormessage) } } @@ -105,8 +111,8 @@ export async function createProfile( phone_number: string | undefined, additionalAttributes: AdditionalAttributes ) { + validateExternalId(external_id) try { - validateExternalId(external_id) const profileData: ProfileData = { data: { type: 'profile', @@ -594,11 +600,7 @@ function validateAndConstructRemoveProfilePayloads(payload: RemoveProfilePayload } if (external_id && external_id.length > MAX_EXTERNAL_ID_LENGTH) { - response.error = { - status: 400, - errortype: 'PAYLOAD_VALIDATION_FAILED', - errormessage: `Length of external_id must be no more than ${MAX_EXTERNAL_ID_LENGTH} characters.` - } + response.error = EXTERNAL_ID_LENGTH_ERROR return response } @@ -633,11 +635,7 @@ function validateAndConstructProfilePayload(payload: AddProfileToListPayload): { } if (external_id && external_id.length > MAX_EXTERNAL_ID_LENGTH) { - response.error = { - status: 400, - errortype: 'PAYLOAD_VALIDATION_FAILED', - errormessage: `Length of external_id must be no more than ${MAX_EXTERNAL_ID_LENGTH} characters.` - } + response.error = EXTERNAL_ID_LENGTH_ERROR return response } @@ -800,11 +798,7 @@ function validateAndPreparePayloads(payloads: TrackEventPayload[], multiStatusRe } if (external_id && external_id.length > MAX_EXTERNAL_ID_LENGTH) { - multiStatusResponse.setErrorResponseAtIndex(originalBatchIndex, { - status: 400, - errortype: 'PAYLOAD_VALIDATION_FAILED', - errormessage: `Length of external_id must be no more than ${MAX_EXTERNAL_ID_LENGTH} characters.` - }) + multiStatusResponse.setErrorResponseAtIndex(originalBatchIndex, EXTERNAL_ID_LENGTH_ERROR) return } @@ -955,11 +949,7 @@ export function validateProfilePayload(payload: Payload): validateProfilePayload } if (payload.external_id && payload.external_id.length > MAX_EXTERNAL_ID_LENGTH) { - response.error = { - status: 400, - errortype: 'PAYLOAD_VALIDATION_FAILED', - errormessage: `Length of external_id must be no more than ${MAX_EXTERNAL_ID_LENGTH} characters.` - } + response.error = EXTERNAL_ID_LENGTH_ERROR return response } diff --git a/packages/destination-actions/src/destinations/klaviyo/upsertProfile/__tests__/index.test.ts b/packages/destination-actions/src/destinations/klaviyo/upsertProfile/__tests__/index.test.ts index 6050e086133..d787f59fb7e 100644 --- a/packages/destination-actions/src/destinations/klaviyo/upsertProfile/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/klaviyo/upsertProfile/__tests__/index.test.ts @@ -50,6 +50,44 @@ describe('Upsert Profile', () => { ) }) + it('should throw error if external_id exceeds 255 characters', async () => { + const longExternalId = 'a'.repeat(256) + const event = createTestEvent({ + type: 'identify', + traits: { + email: 'test@example.com' + } + }) + + const mapping = { + external_id: longExternalId, + email: { '@path': '$.traits.email' } + } + + await expect(testDestination.testAction('upsertProfile', { event, settings, mapping })).rejects.toThrowError( + 'Length of external_id must be no more than 255 characters.' + ) + }) + + it('should not throw error if external_id is exactly 255 characters', async () => { + const exactExternalId = 'a'.repeat(255) + const event = createTestEvent({ + type: 'identify', + traits: { + email: 'test@example.com' + } + }) + + const mapping = { + external_id: exactExternalId, + email: { '@path': '$.traits.email' } + } + + nock(`${API_URL}`).post('/profiles/').reply(200, {}) + + await expect(testDestination.testAction('upsertProfile', { event, settings, mapping })).resolves.not.toThrowError() + }) + it('should throw an error for invalid phone number format in perform', async () => { const event = createTestEvent({ type: 'identify', From 76b89a2576653e829aaf5ba60319708bde35b711 Mon Sep 17 00:00:00 2001 From: Ankit Gupta Date: Wed, 6 May 2026 09:32:14 +0530 Subject: [PATCH 4/5] Added unit test case and types file --- .../klaviyo/addProfileToList/generated-types.ts | 2 +- .../orderCompleted/__tests__/index.test.ts | 17 +++++++++++++++++ .../src/destinations/klaviyo/properties.ts | 6 ++++-- .../removeProfile/__tests__/index.test.ts | 16 ++++++++++++++++ .../__tests__/index.test.ts | 16 ++++++++++++++++ .../removeProfileFromList/generated-types.ts | 2 +- .../klaviyo/trackEvent/__tests__/index.test.ts | 17 +++++++++++++++++ 7 files changed, 72 insertions(+), 4 deletions(-) diff --git a/packages/destination-actions/src/destinations/klaviyo/addProfileToList/generated-types.ts b/packages/destination-actions/src/destinations/klaviyo/addProfileToList/generated-types.ts index 669a43d79ab..e28849b329c 100644 --- a/packages/destination-actions/src/destinations/klaviyo/addProfileToList/generated-types.ts +++ b/packages/destination-actions/src/destinations/klaviyo/addProfileToList/generated-types.ts @@ -14,7 +14,7 @@ export interface Payload { */ list_id: string /** - * A unique identifier used by customers to associate Klaviyo profiles with profiles in an external system. One of External ID and Email required. + * A unique identifier used by customers to associate Klaviyo profiles with profiles in an external system. One of External ID and Email required. Must not exceed 255 characters. */ external_id?: string /** diff --git a/packages/destination-actions/src/destinations/klaviyo/orderCompleted/__tests__/index.test.ts b/packages/destination-actions/src/destinations/klaviyo/orderCompleted/__tests__/index.test.ts index 5f1967d7b9c..3bf60152f51 100644 --- a/packages/destination-actions/src/destinations/klaviyo/orderCompleted/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/klaviyo/orderCompleted/__tests__/index.test.ts @@ -53,6 +53,23 @@ describe('Order Completed', () => { ) }) + it('should throw error if external_id exceeds 255 characters', async () => { + const event = createTestEvent({ + type: 'track', + timestamp: '2022-01-01T00:00:00.000Z' + }) + + const mapping = { + profile: { external_id: 'a'.repeat(256) }, + metric_name: 'Order Completed', + properties: { key: 'value' } + } + + await expect(testDestination.testAction('orderCompleted', { event, mapping, settings })).rejects.toThrowError( + 'Length of external_id must be no more than 255 characters.' + ) + }) + it('should throw an error for invalid phone number format', async () => { const profile = { email: 'test@example.com', phone_number: 'invalid-phone-number', country_code: 'US' } const properties = { key: 'value' } diff --git a/packages/destination-actions/src/destinations/klaviyo/properties.ts b/packages/destination-actions/src/destinations/klaviyo/properties.ts index 30c5ea69eba..4a134ce5951 100644 --- a/packages/destination-actions/src/destinations/klaviyo/properties.ts +++ b/packages/destination-actions/src/destinations/klaviyo/properties.ts @@ -30,8 +30,10 @@ export const email: InputField = { export const external_id: InputField = { label: 'External ID', - description: `A unique identifier used by customers to associate Klaviyo profiles with profiles in an external system. One of External ID and Email required. The length should be between 1 and 255 characters.`, - type: 'string' + description: `A unique identifier used by customers to associate Klaviyo profiles with profiles in an external system. One of External ID and Email required. Must not exceed 255 characters.`, + type: 'string', + minimum: 0, + maximum: 255 } export const enable_batching: InputField = { diff --git a/packages/destination-actions/src/destinations/klaviyo/removeProfile/__tests__/index.test.ts b/packages/destination-actions/src/destinations/klaviyo/removeProfile/__tests__/index.test.ts index 991263b9246..89661890630 100644 --- a/packages/destination-actions/src/destinations/klaviyo/removeProfile/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/klaviyo/removeProfile/__tests__/index.test.ts @@ -25,6 +25,22 @@ describe('Remove Profile', () => { ) }) + it('should throw error if external_id exceeds 255 characters', async () => { + const event = createTestEvent({ + type: 'track', + properties: {} + }) + + const mapping = { + list_id: listId, + external_id: 'a'.repeat(256) + } + + await expect(testDestination.testAction('removeProfile', { event, mapping, settings })).rejects.toThrowError( + 'Length of external_id must be no more than 255 characters.' + ) + }) + it('should throw an error for invalid phone number format', async () => { const event = createTestEvent({ type: 'track', diff --git a/packages/destination-actions/src/destinations/klaviyo/removeProfileFromList/__tests__/index.test.ts b/packages/destination-actions/src/destinations/klaviyo/removeProfileFromList/__tests__/index.test.ts index 4201cfcf445..208738f6cb3 100644 --- a/packages/destination-actions/src/destinations/klaviyo/removeProfileFromList/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/klaviyo/removeProfileFromList/__tests__/index.test.ts @@ -25,6 +25,22 @@ describe('Remove List from Profile', () => { ) }) + it('should throw error if external_id exceeds 255 characters', async () => { + const event = createTestEvent({ + type: 'track', + properties: {} + }) + + const mapping = { + list_id: listId, + external_id: 'a'.repeat(256) + } + + await expect( + testDestination.testAction('removeProfileFromList', { event, mapping, settings }) + ).rejects.toThrowError(AggregateAjvError) + }) + it('should throw an error for invalid phone number format', async () => { const event = createTestEvent({ type: 'track', diff --git a/packages/destination-actions/src/destinations/klaviyo/removeProfileFromList/generated-types.ts b/packages/destination-actions/src/destinations/klaviyo/removeProfileFromList/generated-types.ts index 055596f972b..9b2d49c9513 100644 --- a/packages/destination-actions/src/destinations/klaviyo/removeProfileFromList/generated-types.ts +++ b/packages/destination-actions/src/destinations/klaviyo/removeProfileFromList/generated-types.ts @@ -6,7 +6,7 @@ export interface Payload { */ email?: string /** - * A unique identifier used by customers to associate Klaviyo profiles with profiles in an external system. One of External ID and Email required. + * A unique identifier used by customers to associate Klaviyo profiles with profiles in an external system. One of External ID and Email required. Must not exceed 255 characters. */ external_id?: string /** diff --git a/packages/destination-actions/src/destinations/klaviyo/trackEvent/__tests__/index.test.ts b/packages/destination-actions/src/destinations/klaviyo/trackEvent/__tests__/index.test.ts index 1a319310a53..3c7e7cf64bb 100644 --- a/packages/destination-actions/src/destinations/klaviyo/trackEvent/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/klaviyo/trackEvent/__tests__/index.test.ts @@ -28,6 +28,23 @@ describe('Track Event', () => { ) }) + it('should throw error if external_id exceeds 255 characters', async () => { + const event = createTestEvent({ + type: 'track', + timestamp: '2022-01-01T00:00:00.000Z' + }) + + const mapping = { + profile: { external_id: 'a'.repeat(256) }, + metric_name: 'Test Event', + properties: {} + } + + await expect(testDestination.testAction('trackEvent', { event, mapping, settings })).rejects.toThrowError( + 'Length of external_id must be no more than 255 characters.' + ) + }) + it('should throw an error for invalid phone number format', async () => { const profile = { email: 'test@example.com', phone_number: 'invalid-phone-number', country_code: 'US' } const properties = { key: 'value' } From ca65e6558fdffc154adf4e28bac8263649f99998 Mon Sep 17 00:00:00 2001 From: Ankit Gupta Date: Tue, 12 May 2026 12:25:09 +0530 Subject: [PATCH 5/5] Address PR review: move external_id check before phone mutation and update description - Move external_id length validation before phone_number processing in validateAndPreparePayloads to prevent partial payload mutation on error - Update external_id description to include Phone Number as valid identifier Co-Authored-By: Claude Opus 4.6 --- .../klaviyo/addProfileToList/generated-types.ts | 2 +- .../src/destinations/klaviyo/functions.ts | 15 ++++++--------- .../src/destinations/klaviyo/properties.ts | 2 +- .../removeProfileFromList/__tests__/index.test.ts | 2 +- .../removeProfileFromList/generated-types.ts | 2 +- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/packages/destination-actions/src/destinations/klaviyo/addProfileToList/generated-types.ts b/packages/destination-actions/src/destinations/klaviyo/addProfileToList/generated-types.ts index e28849b329c..cc9a70db301 100644 --- a/packages/destination-actions/src/destinations/klaviyo/addProfileToList/generated-types.ts +++ b/packages/destination-actions/src/destinations/klaviyo/addProfileToList/generated-types.ts @@ -14,7 +14,7 @@ export interface Payload { */ list_id: string /** - * A unique identifier used by customers to associate Klaviyo profiles with profiles in an external system. One of External ID and Email required. Must not exceed 255 characters. + * A unique identifier used by customers to associate Klaviyo profiles with profiles in an external system. One of External ID, Email or Phone Number is required. Must not exceed 255 characters. */ external_id?: string /** diff --git a/packages/destination-actions/src/destinations/klaviyo/functions.ts b/packages/destination-actions/src/destinations/klaviyo/functions.ts index fedb40d4bbf..03f9f871c84 100644 --- a/packages/destination-actions/src/destinations/klaviyo/functions.ts +++ b/packages/destination-actions/src/destinations/klaviyo/functions.ts @@ -779,29 +779,26 @@ function validateAndPreparePayloads(payloads: TrackEventPayload[], multiStatusRe return } + if (external_id && external_id.length > MAX_EXTERNAL_ID_LENGTH) { + multiStatusResponse.setErrorResponseAtIndex(originalBatchIndex, EXTERNAL_ID_LENGTH_ERROR) + return + } + if (phone_number) { - // Validate and convert the phone number if present const validPhoneNumber = validateAndConvertPhoneNumber(phone_number, country_code as string) - // If the phone number is not valid, skip this payload if (!validPhoneNumber) { multiStatusResponse.setErrorResponseAtIndex(originalBatchIndex, { status: 400, errortype: 'PAYLOAD_VALIDATION_FAILED', errormessage: 'Phone number could not be converted to E.164 format.' }) - return // Skip this payload + return } - // Update the payload's phone number with the validated format payload.profile.phone_number = validPhoneNumber delete payload?.profile?.country_code } - if (external_id && external_id.length > MAX_EXTERNAL_ID_LENGTH) { - multiStatusResponse.setErrorResponseAtIndex(originalBatchIndex, EXTERNAL_ID_LENGTH_ERROR) - return - } - const profileToAdd = constructBulkCreateEventPayload(payload) filteredPayloads.push(profileToAdd as JSONLikeObject) validPayloadIndicesBitmap.push(originalBatchIndex) diff --git a/packages/destination-actions/src/destinations/klaviyo/properties.ts b/packages/destination-actions/src/destinations/klaviyo/properties.ts index 4a134ce5951..74662e30ba7 100644 --- a/packages/destination-actions/src/destinations/klaviyo/properties.ts +++ b/packages/destination-actions/src/destinations/klaviyo/properties.ts @@ -30,7 +30,7 @@ export const email: InputField = { export const external_id: InputField = { label: 'External ID', - description: `A unique identifier used by customers to associate Klaviyo profiles with profiles in an external system. One of External ID and Email required. Must not exceed 255 characters.`, + description: `A unique identifier used by customers to associate Klaviyo profiles with profiles in an external system. One of External ID, Email or Phone Number is required. Must not exceed 255 characters.`, type: 'string', minimum: 0, maximum: 255 diff --git a/packages/destination-actions/src/destinations/klaviyo/removeProfileFromList/__tests__/index.test.ts b/packages/destination-actions/src/destinations/klaviyo/removeProfileFromList/__tests__/index.test.ts index 208738f6cb3..addc34f1246 100644 --- a/packages/destination-actions/src/destinations/klaviyo/removeProfileFromList/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/klaviyo/removeProfileFromList/__tests__/index.test.ts @@ -1,8 +1,8 @@ import nock from 'nock' import { createTestEvent, createTestIntegration } from '@segment/actions-core' +import { AggregateAjvError } from '@segment/ajv-human-errors' import Definition from '../../index' import { API_URL } from '../../config' -import { AggregateAjvError } from '@segment/ajv-human-errors' const testDestination = createTestIntegration(Definition) diff --git a/packages/destination-actions/src/destinations/klaviyo/removeProfileFromList/generated-types.ts b/packages/destination-actions/src/destinations/klaviyo/removeProfileFromList/generated-types.ts index 9b2d49c9513..dd062d87a2f 100644 --- a/packages/destination-actions/src/destinations/klaviyo/removeProfileFromList/generated-types.ts +++ b/packages/destination-actions/src/destinations/klaviyo/removeProfileFromList/generated-types.ts @@ -6,7 +6,7 @@ export interface Payload { */ email?: string /** - * A unique identifier used by customers to associate Klaviyo profiles with profiles in an external system. One of External ID and Email required. Must not exceed 255 characters. + * A unique identifier used by customers to associate Klaviyo profiles with profiles in an external system. One of External ID, Email or Phone Number is required. Must not exceed 255 characters. */ external_id?: string /**