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 Tradedesk, which can be used instead of of third-party cookies. UID2 is a deterministic user identifier based on a user’s Personally Identifiable Information (PII), such as an email address. 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 Tradedesk UID2 documentation.
A UID2 can be used in The Tradedesk 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 Tradedesk (
uid_identifier
,uid2
,uid_timestamp
). The function uses this event spec to send an event to Tealium Collect. For example:
-
Choose a PII attribute to use as an identifier for your users. UID2 currently supports a phone number or an email address identifier.
-
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. - Click the Advanced tab, then click Assign Authentications and add UID2 authentication to your function.
For more information, see Add UID2 Authentication.
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 an email address as the user identifier, but can be adapted to use other user identifiers. An attribute ID is used to get the value of the email attribute. Update the attribute ID with the correct value for the attribute you’re using.
import CryptoES from 'crypto-es'
activate(async ({ visitor, visit, helper }) => {
const genHash = (data) => {
return 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,}))$/
);
};
let 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.
};
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
}
// TODO: Change the attribute_number to point to the populated attribute with the visitor's email
const email = visitor.getAttributeValueById('ATTRIBUTE_ID')
let hashed_email = '';
console.log(email)
const tealium_vid = visitor.properties.visitor_id
if(!tealium_config.email_hashed) {
if(!validateEmail(email)) {
throw new Error(`Email Attribute is not a valid email`);
return false
}
hashed_email = genHash(email)
} else {
hashed_email = email
}
console.log(hashed_email)
// TODO: Update the following variables with your TTD UID2 credentials
const api_key = 'TTD_API_KEY' // TODO: Change TTD_API_KEY to your TTD API Key.
const secret = 'UID2_SECRET' // TODO: Change UID2_SECRET to your TTD UID2 secret.
const url = 'https://prod.uidapi.com/v2/identity/map'
const key = CryptoES.enc.Base64.parse(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 = {
email_hash: [genHash(email)]
}
const {timestamp, nonce, enveloped} = encrypt(payload)
console.log('\n********** encypted **********')
console.log('timestamp:', timestamp)
console.log('nonce:', nonce)
console.log('enveloped:', enveloped)
const response = await fetch(url, {
method: 'POST',
body: enveloped,
headers: {
'Authorization': `Bearer ${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: This event spec is sent to Tealium at the end of the Function. Make sure that the attributes in the event spec you created match the attributes in event_data.
let event_data ={
tealium_event: "UID2_event_data",
tealium_visitor_id: tealium_vid,
uid: data.developed.body.mapped[0].advertising_id,
uid_timestamp : JSON.stringify(data.timestamp)
};
// This is the step that sends the event_data object to Tealium for processing.
console.log(JSON.stringify(event_data))
if(event_data.uid) {
track(event_data, tealium_config)
.then(response => {
if (!response.ok) {
throw new Error(`Network response was not ok. Status code: ${response.status}.`);
}
console.log('Status code:', response.status);
return response.text();
})
.catch(error => console.error('Error:', error.message));
}
else {
console.error("Couldn't generate advertising ID")
}
}
else {
console.log('\nxxxxx failed xxxxx\n')
console.log(JSON.stringify(response))
}
})
This page was last updated: February 20, 2024