Use a function to generate a UID2
This article describes how to use a visitor function to generate a UID2 for a user and save it in the visitor profile.
Unified ID 2.0 (UID2) is an open source ID framework created by The Trade Desk, which can be used instead of third-party cookies. UID2 is a deterministic user identifier based on a user’s Personally Identifiable Information (PII), such as an email address, and the UID is hashed and encrypted to create a UID2 that is returned in the response to a UID2 request. For more information, see The Trade Desk UID2 documentation.
A UID2 can be used in The Trade Desk connector and other supported outbound connectors.
Generate UID2 for a visitor profile
We recommend using a visitor function to generate the UID2. Trigger the function when the visitor has PII but does not have a UID2 assigned. For more information about visitor functions, see About event and visitor functions.
The visitor function does the following:
- Generates a UID2 if the visitor does not have one assigned.
- Sends an event containing the UID2 and
tealium_visitor_id
to Tealium Collect.
The AudienceStream attribute and visitor attribute are enriched by the event sent from the function.
Prerequisites
Before you create a visitor function to generate a UID2, do the following:
- Create a UID2 event spec with attributes for the data that is received from The Trade Desk (
uid_identifier
,uid2
,uid_timestamp
). The function uses this event spec to send an event to Tealium Collect. For example:
- Choose a PII attribute or attributes to identify your users. UID2 currently supports a phone number, an email address identifier, or both. The example code below gives priority to the UID generated by the email address over the one generated by the phone number.
- Create a visitor attribute to store the UID2. Add an enrichment to enrich this visitor attribute with the value of the event UID2 attribute. For more information, see Using Attributes and About enrichments.
- Create an audience for identified visitors (visitors that have an email address, phone number, or other identifier assigned) that do not have a UID2 assigned. For example:
For more information, see Create an audience.
UID2 generator function
When you create your function, do the following:
- For the trigger, select Processed Visitor.
- For Audience, select the audience you created for identified visitors that do not have a UID2 assigned. Leave Trigger On set to the default value, which is
Joined Audience
.
Delete the default code for a processed visitor function, then copy and paste the example code to generate a UID2 shown in Example code. Modify the example code as needed.
Example code
This example code is only a guide for setting up a function to assign a UID2 to visitors and cannot be used without modification. Look for comments in the code with TODO
for the lines that must be modified for your configuration.
This example uses both the email address and phone number as the user identifiers, but can be adapted to use other user identifiers. Attribute IDs are used to get the value of the email and phone attributes. Update the attribute IDs with the correct values for the attributes you’re using.
import CryptoES from 'crypto-es';
activate(async ({ visitor, visit, helper }) => {
const genHash = (data) => CryptoES.SHA256(data).toString(CryptoES.enc.Base64);
const validateEmail = (email) => {
return String(email).toLowerCase().match(
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|.(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
);
};
//TODO: Update the following Trade Desk configuration attributes.
const ttd_config = {
api_key : 'TTD_API_KEY', // TODO: Change TTD_API_KEY to your TTD API Key.
secret : 'UID2_SECRET' // TODO: Change UID2_SECRET to your TTD UID2 secret.
};
// TODO: Update the following Tealium configuration attributes as needed.
const tealium_config = {
tealium_account: 'CURRENT',
tealium_profile: 'CURRENT',
tealium_datasource: 'DATA_SOURCE_KEY', // TODO: Change DATA_SOURCE_KEY to your Tealium Data Source key.
email_hashed: false, // TODO: If your email is already hashed, set this to true.
phone_hashed: false, // TODO: If your phone is already hashed, set this to true.
email_attr_id: 'EMAIL_ATTRIBUTE_ID', // TODO: Change the attribute_number to point to the populated attribute with the visitor's email
phone_attr_id: 'PHONE_ATTRIBUTE_ID', // TODO: Change the attribute_number to point to the populated attribute with the visitor's phone
current_uid2_attr_id: 'CURRENT_UID2_ATTRIBUTE_ID' // TODO: Change the attribute_number to point to the populated attribute with the visitor's UID2
};
if (tealium_config.tealium_account === 'CURRENT') {
tealium_config.tealium_account = visitor.properties.account;
}
if (tealium_config.tealium_profile === 'CURRENT') {
tealium_config.tealium_profile = visitor.properties.profile;
}
const email = visitor.getAttributeValueById(tealium_config.email_attr_id);
const phone = visitor.getAttributeValueById(tealium_config.phone_attr_id);
const current_uid = visitor.getAttributeValueById(tealium_config.current_uid2_attr_id);
const tealium_vid = visitor.properties.visitor_id;
let email_hash = null;
if (email) {
if (!tealium_config.email_hashed) {
if (!validateEmail(email)) {
throw new Error('Email is not valid');
}
email_hash = genHash(email);
} else {
email_hash = email;
}
}
let phone_hash = null;
if (phone) {
phone_hash = tealium_config.phone_hashed ? phone : genHash(phone);
}
if (!email_hash && !phone_hash) {
throw new Error('Must provide at least one identity: email or phone.');
}
const url = 'https://prod.uidapi.com/v3/identity/map';
const key = CryptoES.enc.Base64.parse(ttd_config.secret);
const hexRef = "0123456789abcdef";
const randomBytes = (bytes) => {
let buf = '';
for (let i = 0; i < bytes * 2; i++)
buf += hexRef.charAt(Math.floor(Math.random() * hexRef.length));
return buf;
};
const writeBigUint64BE = (num, bytes = 8) => {
let buf = num.toString(16);
let padding = '';
for (let i = 0; i < bytes * 2 - buf.length; i++)
padding += '0';
return padding + buf;
};
const encrypt = (data) => {
const iv = randomBytes(12);
const nonce = randomBytes(8);
const millisec = Date.now();
const timestamp = writeBigUint64BE(millisec);
const payload = CryptoES.enc.Utf8.parse(JSON.stringify(data));
const body = timestamp + nonce + payload;
const ivBuf = CryptoES.enc.Hex.parse(iv);
const encryptedBody = CryptoES.AES.encrypt(
CryptoES.enc.Hex.parse(body),
key,
{
iv: ivBuf,
mode: CryptoES.mode.GCM,
padding: CryptoES.pad.NoPadding
}
);
const ciphertext = encryptedBody.ciphertext;
const authTag = CryptoES.mode.GCM.mac(CryptoES.algo.AES, key, ivBuf, null, ciphertext).toString();
const enveloped = '01' + iv + ciphertext.toString() + authTag;
return {
timestamp: parseInt(timestamp, 16),
nonce: parseInt(nonce, 16),
enveloped: CryptoES.enc.Hex.parse(enveloped).toString(CryptoES.enc.Base64)
};
};
const decrypt = (data) => {
const buf = CryptoES.enc.Base64.parse(data).toString(CryptoES.enc.Hex);
const iv = CryptoES.enc.Hex.parse(buf.substring(0, 24));
const ciphertext = CryptoES.enc.Hex.parse(buf.substring(24, buf.length - 32));
const tag = buf.substring(buf.length - 32);
const encryptedBody = new CryptoES.lib.CipherParams({ ciphertext: ciphertext });
const decrypted = CryptoES.AES.decrypt(encryptedBody, key, {
iv: iv,
mode: CryptoES.mode.GCM,
padding: CryptoES.pad.NoPadding
}).toString();
const timestamp = decrypted.substring(0, 16);
const nonce = decrypted.substring(16, 32);
const developed = decrypted.substring(32);
return {
timestamp: parseInt(timestamp, 16),
nonce: parseInt(nonce, 16),
developed: JSON.parse(CryptoES.enc.Hex.parse(developed).toString(CryptoES.enc.Utf8))
};
};
const payload = {};
if (email_hash) payload.email_hash = [email_hash];
if (phone_hash) payload.phone_hash = [phone_hash];
const { timestamp, nonce, enveloped } = encrypt(payload);
const response = await fetch(url, {
method: 'POST',
body: enveloped,
headers: {
'Authorization': `Bearer ${ttd_config.api_key}`
}
});
if (response.status === 200) {
const body = await response.text();
// console.log('\n********** response **********')
// console.log(body)
const data = decrypt(body);
// console.log('\n********** decrypted **********')
// console.log('timeStamp:', data.timestamp)
// console.log('nonce:', data.nonce)
// console.log('response:', JSON.stringify(data.developed))
// console.log('\n')
// TODO: UID from email will take precedence. Modify as needed.
const uid_email = data.developed.body?.email_hash?.[0]?.u;
const uid_phone = data.developed.body?.phone_hash?.[0]?.u;
const uid2 = uid_email ?? uid_phone;
// console.log('new uid:' + uid2);
// console.log('current uid:' + current_uid);
// Tealium event is generated only if retreived UID does not match existing visitor UID
if (uid2 && uid2 !== current_uid) {
const event_data = {
tealium_event: "UID2_event_data",
tealium_visitor_id: tealium_vid,
uid: uid2,
uid_timestamp: JSON.stringify(data.timestamp)
};
// This is the step that sends the event_data object to Tealium for processing.
await track(event_data, tealium_config)
.then(response => {
if (!response.ok) {
throw new Error(`Track failed with status ${response.status}`);
}
})
.catch(error => console.error('Error:', error.message));
} else {
// console.log("existing uid. no tealium track call.")
}
} else {
console.error(`UID2 fetch failed: ${response.status}`);
}
});
This page was last updated: September 3, 2025