Overview
Impression tracking detects when a product or banner content becomes visible to a user and fires an event after the product has been viewed for a meaningful amount of time. This is essential for accurate product engagement analytics in ecommerce.
Configuration Values
Established Standards for Product Impressions:
- Threshold: 70% of product visible
- Duration: 1000ms (1 second)
- CSS Visibility: Must not be hidden (display: none, visibility: hidden, opacity: 0)
- Fire Once: Only fires once per product per render cycle
Best Practices
- Use 70% threshold - Ensures user has meaningfully viewed the product
- Enforce 1000ms duration - Prevents accidental impressions from quick scrolling
- Check CSS visibility - Products hidden by CSS should not register impressions
- Clean up observers - Always disconnect when component unmounts
- Reset per render - New product listings should allow products to be impressed again
- Batch analytics calls - Collect multiple impressions before sending to reduce network requests
- Test on real devices - Scroll behavior varies across devices and viewport sizes
Basic Algorithm
WHEN element enters viewport AND meets visibility threshold:
START timer
IF element remains visible for required duration:
FIRE impression event
MARK as tracked
IF element leaves viewport before duration:
CANCEL timer
RESET state
Vanilla JavaScript Implementation
Use the IntersectionObserver API to monitor when product elements enter and exit the viewport. The implementation should:
- Create an IntersectionObserver with a 0.7 threshold
- Track state for each element using a WeakMap (timer reference and fired status)
- When an element becomes visible:
- Verify it's not hidden via CSS (
getComputedStyleto check display, visibility, opacity) - Start a 1000ms setTimeout timer
- Verify it's not hidden via CSS (
- If the element leaves viewport before the timer completes, clear the timer
- Once the timer completes, fire the impression callback and mark the element as tracked
- Provide a cleanup function to disconnect the observer
React Hook Implementation
Create a custom useImpression hook that returns a ref and impression state. The hook should:
- Use
useRefto hold a reference to the DOM element and the timer - Use
useStateto track whether an impression has been fired - Use
useEffectto set up the IntersectionObserver with 0.7 threshold and 1000ms duration defaults - Check CSS visibility using
getComputedStylebefore starting the timer - Return the ref (to attach to the product element) and the
hasImpressionboolean - Clean up the observer and timer on unmount
In your product component, attach the ref to the product element and trigger your analytics callback when hasImpression becomes true using another useEffect.
Batching Multiple Impressions
The Beacon API impression endpoints support sending multiple product and banner impressions in a single API request. This is the recommended approach to reduce network overhead and improve performance.
Impression Payload Structure
Both product results and banners can be batched in the same impression event:
{
"context": {
"userId": "41f87850-65f5-4546-851c-f166109e57b9",
"sessionId": "1c311c14-c33b-4222-952a-f17db729ea31",
"pageLoadId": "080d179f-0034-4278-8d3c-dfba71c9b897",
"timestamp": "2006-01-02T15:04:05Z",
"pageUrl": "https://example.com/search?q=shirt"
},
"data": {
"responseId": "a907d0ad-720a-4666-b046-00c0c28fb78d",
"results": [
{
"type": "product",
"parentId": "8128331911111",
"uid": "8128331907370"
},
{
"type": "banner",
"uid": "8128168700001"
},
{
"type": "product",
"parentId": "8128331922222",
"uid": "8128021070122"
},
{
"type": "product",
"parentId": "8128331933333",
"uid": "8127879741738"
}
],
"banners": [
{
"uid": "8128168755498"
}
]
}
}Empty Arrays
The impression API requires both results and banners arrays in the payload. If you have no impressions for one type, send an empty array:
{
"data": {
"responseId": "a907d0ad-720a-4666-b046-00c0c28fb78d",
"results": [
{ "type": "product", "parentId": "123", "uid": "456" }
],
"banners": []
}
}