Event notification is crucial for attribution and measurement.
Connection: keep-alive in the header) to optimize performance.session_id: Persistent visitor identifier. Required in all events. Must remain consistent during browsing and across sessions within the conversion window (≥ 14 days). The session_id should be unique per user and ideally not expire during this period.user_id: Unique customer ID on the platform, consistent across channels. Required in conversion. Optional (recommended) for impression, view, and click. The user_id must be the same across app, website, and physical store.After identifying the user and the session, follow these guidelines when firing events:
impression_url): Fire the event as soon as you decide to display the Ads returned by the ad server, even if the Ad ultimately is not shown to the user (e.g., due to layout changes or blocking).view_url): Fire the event when at least 50% of the Ad stays within the user’s viewport for a continuous 1 second.click_url): Fire the event every time the user clicks the Ad.There is no need to persist event state across page loads, but make sure each event is sent only once for the same Ad and context.
Send a POST request to the respective event URL (impression_url, view_url, click_url) provided in the ad query, with a JSON body containing session_id (required) and user_id (when available).
impression_url when the Ad is rendered; view_url when the Ad is visible (if you measure viewability); click_url on user click.Content-Type: application/json header.navigator.sendBeacon() to ensure asynchronous sending without blocking navigation and prevent loss of events when the user navigates to another page or closes the browser.HTTP 202 Accepted.See browser event sending examples in examples/EVENTS_IMPRESSIONS_VIEWS_CLICKS_BROWSER.md.
Example of sending an event using Beacon API:
// Function to send impression event
function sendImpressionEvent(impressionUrl, userId, sessionId) {
// Event data
const eventData = {
user_id: userId,
session_id: sessionId
};
// Check if browser supports Beacon API
if (navigator.sendBeacon) {
// Convert data to JSON
const blob = new Blob([JSON.stringify(eventData)], {type: 'application/json'});
// Send event asynchronously
const success = navigator.sendBeacon(impressionUrl, blob);
if (!success) {
console.error('Failed to send event via Beacon API');
// Implement fallback if needed
}
} else {
// Fallback for browsers that don't support Beacon API
const xhr = new XMLHttpRequest();
xhr.open('POST', impressionUrl, true); // true for asynchronous
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(eventData));
}
}
// Example usage
sendImpressionEvent(
'https://events.newtail-media.newtail.com.br/v1/beacon/impression/123456',
'user-123',
'session-456'
);
Important: Using the Beacon API is mandatory to ensure that events are sent even when the user navigates to another page or closes the browser. This prevents loss of events and ensures more accurate measurement.
When a purchase is completed, send the order data.
POST https://events.newtail-media.newtail.com.br/v1/beacon/conversionContent-Type: application/json header.See conversion examples in examples/EVENTS_CONVERSION_BROWSER.md (Browser) and examples/EVENTS_CONVERSION_CURL.md (cURL).
| Order Field | Description | Type | Required? |
|---|---|---|---|
publisher_id |
Publisher ID. | String | Yes |
user_id |
ID of the user who made the purchase. | String | Yes |
session_id |
Session ID of the purchase. | String | Yes |
order_id |
Order ID. | String | Yes |
created_at |
Order date/time (ISO 8601 in UTC). | String | Yes |
items |
List of order items. | Array[Item] | Yes |
channel |
Sales channel (e.g., ecommerce, app, physical store). | String | Yes |
brand |
Brand/site where the sale occurred (required when there is more than one site). | String | No/Yes |
gender |
Indicates the customer’s gender. F: for female, M: for male, O: for others, null: for unidentified. | String | No (Recommended) |
uf |
Indicates the state of the order purchase. | String | No (Recommended) |
city |
Indicates the city where the customer made the purchase. | String | No (Recommended) |
is_company |
Indicates if the sale was made to a company or individual. | Boolean | No (Recommended) |
email_hashed |
User’s email in hash format (SHA256). | String | Yes |
phone_hashed |
User’s phone in hash (SHA256). | String | No (Recommended) |
social_id_hashed |
User’s Tax ID (CPF/CNPJ) in hash (SHA256). | String | No (Recommended) |
first_name_hashed |
User’s First Name (hashed). | String | No (Recommended) |
last_name_hashed |
User’s Last Name (hashed). | String | No (Recommended) |
Recommended normalization for hashing:
email_hashed: lowercase and trim the email; SHA-256 hash in hexadecimal.phone_hashed: phone in E.164 format (e.g., +15551234567), no masks; SHA-256 hash in hexadecimal.social_id_hashed: tax ID digits only (no punctuation); SHA-256 hash in hexadecimal.first_name_hashed/last_name_hashed: names normalized (trim, no double spaces); SHA-256 hash in hexadecimal.
| Field | Description | Type | Required |
|---|---|---|---|
sku |
Unique product SKU identifier | String | Required |
quantity |
Quantity of product purchased | Double | Required |
price |
Original product price (list price) | Double | Required |
promotional_price |
Promotional product price (sale price) | Double | Required |
seller_id |
Seller identifier | String | Optional |
product_id |
Unique product identifier that encompasses the SKU | String | Optional |
price and promotional_price fields must contain the unit value of the product, not multiplied by quantityprice and promotional_price are not provided correctly, the conversion cannot be measured{
"sku": "12221",
"seller_id": "1234",
"product_id": "4567",
"quantity": 1,
"price": 2000.00,
"promotional_price": 1899.00
}
POST https://events.newtail-media.newtail.com.br/v1/beacon/conversion HTTP/1.1
Accept: application/json
Content-Type: application/json
{
"channel": "ecommerce",
"publisher_id": "xxx",
"user_id": "6f92d1e9-00b6-4f8b-9645-faeab321e1cc",
"session_id": "5898b8d1-c250-4bb5-931b-8b9d0ee7b499",
"order_id": "123",
"email_hashed": "xyz",
"items": [
{
"sku": "12221",
"seller_id": "1234",
"product_id": "4567",
"quantity": 1,
"price": 2000.00,
"promotional_price": 1899.00
},
{
"sku": "12222",
"seller_id": null,
"product_id": "4568",
"quantity": 2,
"price": 500.00,
"promotional_price": 400.00
}
],
"created_at": "2023-01-01T09:20:00Z"
}
Note: In the example above, observe that:
{
"messages": [
"conversion will be processed soon"
]
}
The API returns validation errors in a JSON Schema–compatible (Ajv-like) format.
Example error response:
[
{
"instancePath": "",
"keyword": "required",
"message": "must have required property 'user_id'",
"params": {
"missingProperty": "user_id"
},
"schemaPath": "#/required"
},
{
"instancePath": "",
"keyword": "required",
"message": "must have required property 'order_id'",
"params": {
"missingProperty": "order_id"
},
"schemaPath": "#/required"
},
{
"instancePath": "",
"keyword": "required",
"message": "must have required property 'publisher_id'",
"params": {
"missingProperty": "publisher_id"
},
"schemaPath": "#/required"
},
{
"instancePath": "",
"keyword": "required",
"message": "must have required property 'items'",
"params": {
"missingProperty": "items"
},
"schemaPath": "#/required"
},
{
"instancePath": "",
"keyword": "required",
"message": "must have required property 'created_at'",
"params": {
"missingProperty": "created_at"
},
"schemaPath": "#/required"
}
]
Conversions are only computed for campaigns when a product match occurs. This means:
Scenario 1 - With Match (Conversion Computed)
Scenario 2 - Without Match (Conversion NOT Computed)
const sendConversion = async (orderData) => {
const payload = {
channel: "ecommerce",
publisher_id: "xxx",
user_id: orderData.userId,
session_id: orderData.sessionId,
order_id: orderData.orderId,
email_hashed: orderData.emailHash,
items: orderData.items.map(item => ({
sku: item.sku,
seller_id: item.sellerId || null,
product_id: item.productId || null,
quantity: item.quantity,
price: item.unitPrice, // Unit value, not multiplied
promotional_price: item.unitPromotionalPrice // Unit value, not multiplied
})),
created_at: new Date().toISOString()
};
try {
// Convert data to JSON
const blob = new Blob([JSON.stringify(payload)], {type: 'application/json'});
// Send event asynchronously using Beacon API
if (navigator.sendBeacon) {
const success = navigator.sendBeacon(
'https://events.newtail-media.newtail.com.br/v1/beacon/conversion',
blob
);
if (success) {
console.log('Conversion sent successfully');
} else {
console.error('Failed to send conversion via Beacon API');
}
} else {
// Fallback for browsers that don't support Beacon API
const response = await fetch('https://events.newtail-media.newtail.com.br/v1/beacon/conversion', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
if (response.status === 202) {
console.log('Conversion sent successfully');
} else if (response.status === 422) {
const errors = await response.json();
console.error('Validation errors:', errors);
}
}
} catch (error) {
console.error('Error sending conversion:', error);
}
};
// Example usage
const order = {
userId: "6f92d1e9-00b6-4f8b-9645-faeab321e1cc",
sessionId: "5898b8d1-c250-4bb5-931b-8b9d0ee7b499",
orderId: "123",
emailHash: "xyz",
items: [
{
sku: "12221",
sellerId: "1234",
productId: "4567",
quantity: 1,
unitPrice: 2000.00,
unitPromotionalPrice: 1899.00
}
]
};
sendConversion(order);
product): 14 days.display/video): 14 days.order_id (re-sending the same order_id within 30 days is ignored).