{"version":3,"sources":["../../src/server/linkedin-conversions-api.ts"],"sourcesContent":["/**\n * Conversions API Payload Builder: https://www.linkedin.com/developers/payload-builder\n * https://learn.microsoft.com/en-us/linkedin/marketing/conversions/conversions-overview?view=li-lms-2025-09\n */\nimport { createHash } from 'crypto';\nimport { fetch } from '@shware/utils';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport type { TrackEvent, UserProvidedData } from '../track/types';\nimport { getFirst } from '../utils/field';\n\ntype UserIdType =\n  | 'SHA256_EMAIL'\n  | 'LINKEDIN_FIRST_PARTY_ADS_TRACKING_UUID'\n  | 'ACXIOM_ID'\n  | 'ORACLE_MOAT_ID';\n\nexport interface CreateLinkedinEventDTO {\n  /**\n   * For any conversion that you want to send through multiple methods, such as Insight Tag and\n   * Conversions API, you must create a conversion rule for each data source (browser and server).\n   * Then, you can implement a logic to pick up the eventId from the browser and send it with the\n   * corresponding event from your server. If we receive an Insight Tag event and a Conversions API\n   * event from the same account with the same eventId, we discard the Conversions API event and\n   * count only the Insight Tag event in campaign reporting.\n   */\n  eventId?: string;\n\n  /**\n   * Replace <id> with the conversion ID extracted when creating the conversion rule\n   * (e.g. urn:lla:llaPartnerConversion:<id>).\n   */\n  conversion: `urn:lla:llaPartnerConversion:${number}`;\n\n  /** Epoch timestamp in milliseconds at which the conversion event happened. */\n  conversionHappenedAt: number;\n  conversionValue: { currencyCode: string; amount: string };\n  user: {\n    userIds: { idType: UserIdType; idValue: string }[];\n    userInfo?: {\n      firstName?: string;\n      lastName?: string;\n      companyName?: string;\n      countryCode?: string;\n      title?: string;\n    };\n\n    /**\n     * The maximum supported size of the list is 1 at the moment. If the list contains multiple\n     * values, only the first value will be used.\n     */\n    externalIds?: [string, ...string[]];\n\n    /**\n     * This is generated when users submit the Linkedin Lead-gen form\n     * (e.g. urn:li:leadGenFormResponse:<id>).\n     */\n    lead?: `urn:li:leadGenFormResponse:${string}`;\n  };\n}\n\nexport interface CreateMultipleLinkedinEventsDTO {\n  elements: CreateLinkedinEventDTO[];\n}\n\nexport type LinkedinConversionConfig = Record<Lowercase<string>, number>;\n\nexport async function sendEvents(\n  accessToken: string,\n  config: LinkedinConversionConfig,\n  // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n  events: TrackEvent<any>[],\n  data: UserProvidedData = {}\n) {\n  const eventNames = Object.keys(config);\n  const address = getFirst(data.address);\n  const userIds: { idType: UserIdType; idValue: string }[] = [];\n  const externalIds: [string, ...string[]] | undefined = data.user_id ? [data.user_id] : undefined;\n  const userInfo =\n    address && address.first_name && address.last_name\n      ? {\n          firstName: address.first_name,\n          lastName: address.last_name,\n          countryCode: address.country,\n        }\n      : undefined;\n\n  if (data.email) {\n    const email = getFirst(data.email);\n    if (email)\n      userIds.push({\n        idType: 'SHA256_EMAIL',\n        idValue: createHash('sha256').update(email).digest('hex'),\n      });\n  }\n\n  const dto: CreateMultipleLinkedinEventsDTO = {\n    elements: events\n      .filter((event) => eventNames.includes(event.name) && !IGNORED_EVENTS.includes(event.name))\n      .map((event) => ({\n        eventId: event.id,\n        conversion: `urn:lla:llaPartnerConversion:${config[event.name]}`,\n        conversionHappenedAt: Date.now(),\n        conversionValue: {\n          currencyCode: event.properties?.currency?.toUpperCase() ?? 'USD',\n          amount: event.properties?.value?.toString() ?? '0',\n        },\n        user: {\n          userIds: event.tags.li_fat_id\n            ? [\n                {\n                  idType: 'LINKEDIN_FIRST_PARTY_ADS_TRACKING_UUID',\n                  idValue: event.tags.li_fat_id,\n                },\n                ...userIds,\n              ]\n            : userIds,\n          userInfo,\n          externalIds,\n        },\n      })),\n  };\n\n  if (dto.elements.length === 0) return;\n  try {\n    const response = await fetch('https://api.linkedin.com/rest/conversionEvents', {\n      method: 'POST',\n      headers: {\n        Authorization: `Bearer ${accessToken}`,\n        'Content-Type': 'application/json',\n        'LinkedIn-Version': '202509',\n        'X-Restli-Protocol-Version': '2.0.0',\n        'X-RestLi-Method': 'BATCH_CREATE',\n      },\n      body: JSON.stringify(dto),\n    });\n\n    if (response.ok) return;\n    const { status } = response;\n    const message = await response.text();\n    console.error(`Failed to send LinkedIn conversion, status: ${status}, body: ${message}`);\n  } catch (error) {\n    console.error('Failed to send LinkedIn conversion, network error:', error);\n  }\n}\n"],"mappings":";AAIA,SAAS,kBAAkB;AAC3B,SAAS,aAAa;AACtB,SAAS,sBAAsB;AAE/B,SAAS,gBAAgB;AA0DzB,eAAsB,WACpB,aACA,QAEA,QACA,OAAyB,CAAC,GAC1B;AACA,QAAM,aAAa,OAAO,KAAK,MAAM;AACrC,QAAM,UAAU,SAAS,KAAK,OAAO;AACrC,QAAM,UAAqD,CAAC;AAC5D,QAAM,cAAiD,KAAK,UAAU,CAAC,KAAK,OAAO,IAAI;AACvF,QAAM,WACJ,WAAW,QAAQ,cAAc,QAAQ,YACrC;AAAA,IACE,WAAW,QAAQ;AAAA,IACnB,UAAU,QAAQ;AAAA,IAClB,aAAa,QAAQ;AAAA,EACvB,IACA;AAEN,MAAI,KAAK,OAAO;AACd,UAAM,QAAQ,SAAS,KAAK,KAAK;AACjC,QAAI;AACF,cAAQ,KAAK;AAAA,QACX,QAAQ;AAAA,QACR,SAAS,WAAW,QAAQ,EAAE,OAAO,KAAK,EAAE,OAAO,KAAK;AAAA,MAC1D,CAAC;AAAA,EACL;AAEA,QAAM,MAAuC;AAAA,IAC3C,UAAU,OACP,OAAO,CAAC,UAAU,WAAW,SAAS,MAAM,IAAI,KAAK,CAAC,eAAe,SAAS,MAAM,IAAI,CAAC,EACzF,IAAI,CAAC,UAAO;AAlGnB;AAkGuB;AAAA,QACf,SAAS,MAAM;AAAA,QACf,YAAY,gCAAgC,OAAO,MAAM,IAAI,CAAC;AAAA,QAC9D,sBAAsB,KAAK,IAAI;AAAA,QAC/B,iBAAiB;AAAA,UACf,gBAAc,iBAAM,eAAN,mBAAkB,aAAlB,mBAA4B,kBAAiB;AAAA,UAC3D,UAAQ,iBAAM,eAAN,mBAAkB,UAAlB,mBAAyB,eAAc;AAAA,QACjD;AAAA,QACA,MAAM;AAAA,UACJ,SAAS,MAAM,KAAK,YAChB;AAAA,YACE;AAAA,cACE,QAAQ;AAAA,cACR,SAAS,MAAM,KAAK;AAAA,YACtB;AAAA,YACA,GAAG;AAAA,UACL,IACA;AAAA,UACJ;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,KAAE;AAAA,EACN;AAEA,MAAI,IAAI,SAAS,WAAW,EAAG;AAC/B,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,kDAAkD;AAAA,MAC7E,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,eAAe,UAAU,WAAW;AAAA,QACpC,gBAAgB;AAAA,QAChB,oBAAoB;AAAA,QACpB,6BAA6B;AAAA,QAC7B,mBAAmB;AAAA,MACrB;AAAA,MACA,MAAM,KAAK,UAAU,GAAG;AAAA,IAC1B,CAAC;AAED,QAAI,SAAS,GAAI;AACjB,UAAM,EAAE,OAAO,IAAI;AACnB,UAAM,UAAU,MAAM,SAAS,KAAK;AACpC,YAAQ,MAAM,+CAA+C,MAAM,WAAW,OAAO,EAAE;AAAA,EACzF,SAAS,OAAO;AACd,YAAQ,MAAM,sDAAsD,KAAK;AAAA,EAC3E;AACF;","names":[]}