Skip to content

@trackbridge/sdk/core

The shared kernel: type definitions, normalization rules for enhanced-conversion fields, and the SHA-256 helper used identically on browser and server. Both @trackbridge/sdk/browser and @trackbridge/sdk/server depend on this package.

You’ll rarely import from @trackbridge/sdk/core yourself. The cases where you might:

  • You’re building tooling that needs to inspect what the SDK will send to Google before it sends it (for testing or auditing).
  • You’re hashing identity fields somewhere outside the conversion path and want to use the same normalization as the SDK so values match.
  • You’re extending Trackbridge with a new transport (CAPI, TikTok, etc.) and want to reuse the normalization layer.

Outside those cases, prefer the browser/server packages.

import {
hashSha256,
hashUserData,
normalizeAddress,
normalizeEmail,
normalizeName,
normalizePhone,
GA4_EVENT_NAMES,
} from '@trackbridge/sdk/core';
import type {
Address,
ClickIdentifiers,
ConsentState,
ConsentUpdate,
ConsentValue,
HashedAddress,
HashedUserData,
TrackbridgeContext,
TrackbridgeItem,
UserData,
} from '@trackbridge/sdk/core';

ClickIdentifiers is exported from @trackbridge/sdk/browser; the type is re-exported here for the rare case (e.g. building a custom server-side adapter) where you need it without pulling in the browser entry.

hashSha256(input: string): Promise<string>

Section titled “hashSha256(input: string): Promise<string>”
function hashSha256(input: string): Promise<string>;

Computes the SHA-256 hash of input’s UTF-8 byte representation. Returns 64 lowercase hex characters. The encoding is fixed to UTF-8 internally so browser and server cannot disagree on bytes.

The caller is responsible for normalizing the string first — hashSha256 does no transformation.

await hashSha256('hello'); // → '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'

hashUserData(input: UserData): Promise<HashedUserData>

Section titled “hashUserData(input: UserData): Promise<HashedUserData>”
function hashUserData(input: UserData): Promise<HashedUserData>;

Normalizes and SHA-256-hashes every field of a UserData object that Google expects hashed (email, phone, firstName, lastName, address.street). Address-locality fields (city, region, postalCode, country) are normalized but not hashed — Google requires them in plaintext.

Fields that are absent on input or normalize to an empty string are omitted from the result. The address key is omitted entirely when no sub-field survives normalization.

await hashUserData({
email: ' Alice@Example.COM ',
address: { country: 'us' },
});
// → {
// email: 'd9f...64-hex...',
// address: { country: 'US' }, // normalized to ISO-3166 alpha-2 uppercase, not hashed
// }
function normalizeEmail(input: string): string;

Trim → lowercase → Unicode NFC. Returns '' for input that trims to empty.

function normalizePhone(input: string): string;

Strips whitespace and non-digit characters; preserves a leading + if present. Does not infer a country code (which would depend on runtime locale and would silently diverge between client and server). Returns digits only when no + is present, or +<digits> when one is.

normalizePhone(' (555) 123-4567 '); // → '5551234567'
normalizePhone('+1 (555) 123-4567'); // → '+15551234567'
function normalizeName(input: string): string;

Trim → lowercase → NFC. Preserves hyphens, apostrophes, internal spaces, and diacritics in their NFC form.

function normalizeAddress(input: Address): Address;

Field-by-field normalization:

FieldRule
street, city, regiontrim → lowercase → NFC
postalCodetrim → lowercase. Internal spaces preserved (UK SW1A 1AA stays as written).
countrytrim → uppercase → NFC. Convention: ISO 3166-1 alpha-2 (US, GB, DE).

Undefined input fields are omitted from the result. Fields present but normalizing to an empty string are kept as empty.

type UserData = {
email?: string;
phone?: string;
firstName?: string;
lastName?: string;
address?: Address;
};
type Address = {
street?: string;
city?: string;
region?: string;
postalCode?: string;
country?: string;
};
type HashedUserData = {
email?: string;
phone?: string;
firstName?: string;
lastName?: string;
address?: HashedAddress;
};
type HashedAddress = {
street?: string;
city?: string;
region?: string;
postalCode?: string;
country?: string;
};

HashedUserData and HashedAddress are structurally identical to their unhashed counterparts. The string fields hold lowercase hex SHA-256 digests instead of plaintext.

type ConsentValue = 'granted' | 'denied';
type ConsentUpdate = {
ad_storage?: ConsentValue | 'unknown';
ad_user_data?: ConsentValue | 'unknown';
ad_personalization?: ConsentValue | 'unknown';
analytics_storage?: ConsentValue | 'unknown';
};
type ConsentState = {
ad_storage: ConsentValue | 'unknown';
ad_user_data: ConsentValue | 'unknown';
ad_personalization: ConsentValue | 'unknown';
analytics_storage: ConsentValue | 'unknown';
};

ConsentValue is the two-state CMP signal; 'unknown' is added in ConsentUpdate and ConsentState to model “not reported yet”. Under consentMode: 'off' all signals start 'granted'; under consentMode: 'v2' all signals start 'unknown' until your CMP reports.

The browser SDK only acts on ad_storage (gates _tb_* cookie writes) and ad_user_data (gates outbound PII). The other two signals are stored verbatim from updateConsent so banners can read them back via getConsent().

type ClickIdentifiers = {
gclid?: string;
gbraid?: string;
wbraid?: string;
};

The three Google Ads click identifiers Trackbridge captures from URL params on landing:

  • gclid — standard click ID for web traffic.
  • gbraid — iOS app campaigns under ATT restrictions.
  • wbraid — web equivalent for ATT-restricted scenarios.

Typically only one is set per user. The browser tracker captures whichever appears in the URL on landing and persists it according to clickIdentifierStorage.

type TrackbridgeContext = {
v: 1;
createdAt: number;
clientId?: string;
sessionId?: string;
userId?: string;
clickIds: { gclid?: string; gbraid?: string; wbraid?: string };
consent: ConsentState;
userData?: UserData;
};

The serializable envelope produced by tracker.exportContext() and consumed by serverTracker.fromContext(). Round-trips through JSON.stringify losslessly. v: 1 is the wire-format version — fromContext throws on unknown versions.

See The context envelope for the why and the full pattern.

type TrackbridgeItem = {
itemId?: string;
itemName?: string;
affiliation?: string;
coupon?: string;
creativeName?: string;
creativeSlot?: string;
discount?: number;
index?: number;
itemBrand?: string;
itemCategory?: string;
itemCategory2?: string;
itemCategory3?: string;
itemCategory4?: string;
itemCategory5?: string;
itemListId?: string;
itemListName?: string;
itemVariant?: string;
locationId?: string;
price?: number;
promotionId?: string;
promotionName?: string;
quantity?: number;
};

GA4 ecommerce item shape used by all five semantic helpers. The SDK converts camelCase (itemId) to snake_case (item_id) at serialization time, identically on browser and server.

Per GA4’s spec one of itemId / itemName should be set per item; the SDK leaves both optional and trusts the caller. Per-item currency is not modeled in v1 — event-level currency covers the cases we’ve seen.

const GA4_EVENT_NAMES = {
purchase: 'purchase',
beginCheckout: 'begin_checkout',
addToCart: 'add_to_cart',
signUp: 'sign_up',
refund: 'refund',
} as const;

The internal canonical-name map for the five semantic helpers. Single source of truth for the wire names used by gtag and GA4 MP. Exported so consumers building their own helper-style abstractions (or filtering analytics events) don’t have to copy-paste the mapping.