How Heap Works
Heap operates on an auto-capture model that records every user interaction from the moment the tracking script is installed. Unlike event-based platforms that require manual instrumentation, Heap attaches listeners for clicks, form submissions, page views, field changes, and other DOM interactions at the document level, capturing them all by default.
Each captured interaction is stored as a raw event with automatic properties:
- Target element: CSS selector path, tag name, text content,
id,class,name,hrefattributes - Page context: URL, title, referrer, query parameters
- Session context: Session ID, user ID (if identified), device type, browser, OS, viewport dimensions
- Timing: Timestamp, time since session start, time since last interaction
These raw events are stored in Heap's backend and become retroactively queryable. When you define a named event in the Heap UI (e.g., "Clicked Add to Cart"), Heap applies that definition against all historical raw data, not just data collected after the definition. This is the retroactive analysis model: define events at any point and get historical data immediately.
Heap uses two primary identification mechanisms:
- Device identity: A first-party cookie (
_hp2_id.{APP_ID}) with a 13-month rolling expiry, tracking the device across sessions - User identity: An explicit identity set via
heap.identify(), which merges device-level sessions under a single user profile
Data transmits to https://heapanalytics.com/h via POST requests with JSON payloads. Events are batched client-side and flushed every few seconds or on page unload.
Installing the Tracking Script
Add the Heap snippet inside <head>. Replace APP_ID with your numeric environment ID from Project > Settings > Environment:
<script type="text/javascript">
window.heap=window.heap||[],heap.load=function(e,t){window.heap.appid=e,window.heap.config=t=t||{};var r=document.createElement("script");r.type="text/javascript",r.async=!0,r.src="https://cdn.heapanalytics.com/js/heap-"+e+".js";var a=document.getElementsByTagName("script")[0];a.parentNode.insertBefore(r,a);for(var n=function(e){return function(){heap.push([e].concat(Array.prototype.slice.call(arguments,0)))}},p=["addEventProperties","addUserProperties","clearEventProperties","identify","resetIdentity","removeEventProperty","setEventProperties","track","unsetEventProperty"],o=0;o<p.length;o++)heap[p[o]]=n(p[o])};
heap.load("APP_ID");
</script>
For npm installations:
npm install @heap/heap-js
import heap from '@heap/heap-js';
heap.load('APP_ID');
Configuration options passed as the second argument to heap.load():
heap.load('APP_ID', {
disableTextCapture: false, // Set true to stop capturing text content
secureCookie: true, // Set cookie with Secure flag
trackingServer: 'custom.yourdomain.com' // First-party proxy endpoint
});
Single-page application support: Heap automatically tracks pushState and replaceState calls as pageview events. No additional configuration is needed for React Router, Vue Router, Next.js, or Nuxt.
Content Security Policy:
script-src: https://cdn.heapanalytics.com
connect-src: https://heapanalytics.com
Event Tracking and Data Collection
Auto-Captured Events
Once installed, Heap captures these event types without any code:
| Event Type | Trigger | Properties Captured |
|---|---|---|
| Pageview | URL change (including pushState) | URL, title, referrer, query params |
| Click | mousedown on any element |
Target selector, text, href, class, id |
| Submit | <form> submit event |
Form action, method, field names |
| Change | Input value change (blur on text fields, change on selects/checkboxes) |
Field name, target selector |
Custom Events with heap.track()
Track business logic events that do not correspond to a DOM interaction:
heap.track('Purchase Completed', {
order_id: 'ORD-12345',
revenue: 149.99,
currency: 'USD',
item_count: 3,
payment_method: 'credit_card'
});
Properties can be strings, numbers, or booleans. Nested objects are not supported; flatten before sending.
// Track feature usage
heap.track('Export Generated', {
format: 'csv',
row_count: 15000,
duration_ms: 2340
});
// Track errors
heap.track('API Error', {
endpoint: '/api/v2/orders',
status_code: 500,
error_message: 'Internal Server Error'
});
Event Properties (Session-Scoped)
Attach properties to all subsequent events in the current pageview:
// All events on this page will include these properties
heap.addEventProperties({
experiment_group: 'variant_b',
page_category: 'checkout'
});
// Remove a specific event property
heap.removeEventProperty('experiment_group');
// Clear all event properties
heap.clearEventProperties();
Visual Labeling (Retroactive Event Definition)
In the Heap UI, use the Visual Labeling tool to define events by pointing and clicking on elements in your site. Heap applies these definitions retroactively to all historical data.
The visual labeler generates CSS selector rules internally. For example, defining "Add to Cart Click" might produce: button.add-to-cart on pages matching /products/*. All past raw click events matching that selector and URL pattern are retroactively tagged.
This is the primary mechanism for non-technical users to define events without writing code.
Identity and User Tracking
User Identification
Call heap.identify() after authentication to associate the anonymous device identity with a known user:
heap.identify('user_12345');
The identity parameter must be a string. Heap merges all previous anonymous sessions from the same device cookie into this user's profile. Subsequent sessions on the same device are automatically attributed to the identified user.
User Properties
Attach persistent properties to the identified user:
heap.addUserProperties({
email: 'jane@example.com',
plan: 'enterprise',
company: 'Acme Corp',
role: 'admin',
signup_date: '2024-06-15'
});
User properties persist across sessions and are available for segmentation in all Heap analysis tools (funnels, retention, paths, etc.).
Identity Reset
On logout, reset the identity to prevent the next user on the same device from being merged:
heap.resetIdentity();
This clears the user ID and generates a new anonymous device identity.
Server-Side Identity
For backend-driven identification, use the Heap Server-Side API:
curl -X POST https://heapanalytics.com/api/identify \
-H "Content-Type: application/json" \
-d '{
"app_id": "APP_ID",
"identity": "user_12345",
"properties": {
"plan": "enterprise",
"mrr": 499
}
}'
API and Data Export
Connect API (SQL Access)
Heap Connect syncs your Heap data to your data warehouse (Snowflake, BigQuery, Redshift, S3) on a recurring schedule. Once connected, you get raw tables:
heap.all_events-- Every captured event with all propertiesheap.users-- User profiles with all user propertiesheap.sessions-- Session-level aggregatesheap.pageviews-- Pageview events with URL and referrer data
Example queries against the warehouse:
-- Conversion funnel: signup page → pricing page → checkout
SELECT
COUNT(DISTINCT CASE WHEN step = 1 THEN user_id END) AS saw_signup,
COUNT(DISTINCT CASE WHEN step = 2 THEN user_id END) AS saw_pricing,
COUNT(DISTINCT CASE WHEN step = 3 THEN user_id END) AS completed_checkout
FROM (
SELECT user_id,
CASE
WHEN path = '/signup' THEN 1
WHEN path = '/pricing' THEN 2
WHEN event_name = 'Purchase Completed' THEN 3
END AS step
FROM heap.all_events
WHERE time > CURRENT_DATE - INTERVAL '30 days'
) funnel;
-- Effort analysis: average clicks before completing a task
SELECT
u.identity,
COUNT(e.event_id) AS interactions,
MIN(e.time) AS first_event,
MAX(e.time) AS last_event,
EXTRACT(EPOCH FROM MAX(e.time) - MIN(e.time)) AS duration_seconds
FROM heap.all_events e
JOIN heap.users u ON e.user_id = u.user_id
WHERE e.session_id IN (
SELECT session_id FROM heap.all_events
WHERE event_name = 'Completed Onboarding'
)
GROUP BY u.identity;
Server-Side Track API
Send events from your backend:
curl -X POST https://heapanalytics.com/api/track \
-H "Content-Type: application/json" \
-d '{
"app_id": "APP_ID",
"identity": "user_12345",
"event": "Subscription Renewed",
"properties": {
"plan": "enterprise",
"amount": 499.00,
"interval": "monthly"
}
}'
Bulk Import API
Import historical events for backfill:
curl -X POST https://heapanalytics.com/api/track \
-H "Content-Type: application/json" \
-d '{
"app_id": "APP_ID",
"identity": "user_12345",
"event": "Historical Purchase",
"timestamp": "2024-01-15T10:30:00Z",
"properties": {
"amount": 299.00
}
}'
Common Issues
Auto-captured events are too noisy: Heap captures every click and form interaction, producing a high volume of raw events. Use Heap's event labeling to define meaningful named events and filter dashboards to only those labeled events. The raw data still exists but does not need to surface in analysis.
Retroactive event definitions return zero results: The visual labeler relies on CSS selectors matching current DOM structure. If the site's HTML structure changed since the historical data was collected, the selector will not match old events. Create multiple event definitions to cover different DOM structures across time periods.
Identity merge creates duplicate users: Calling heap.identify() with different IDs on the same device creates separate user profiles. Ensure your application calls heap.resetIdentity() on logout before a different user logs in on the same device.
Custom event properties are missing: Heap silently drops properties with null or undefined values. Validate that all property values are defined before calling heap.track(). Nested objects are flattened to [object Object] strings; flatten them manually.
Connect data is delayed: Heap Connect syncs data on a schedule (typically every few hours). Real-time analysis should use the Heap web UI. Warehouse queries reflect data with a lag of 2-6 hours depending on your sync configuration.
Autocaptured text contains PII: By default, Heap captures text content of clicked elements. On pages with user-generated content or PII displayed in the UI, enable disableTextCapture: true in the heap.load() configuration or use heap-ignore CSS class on specific elements:
<span class="heap-ignore">user@email.com</span>
Platform-Specific Considerations
Auto-capture versus manual instrumentation trade-offs: Heap's auto-capture stores raw interaction data keyed to CSS selectors. This works well for stable UIs but creates fragility when CSS class names are dynamically generated (CSS-in-JS libraries like styled-components or Emotion generate unique class hashes on each build). Add stable data-heap-id attributes to critical UI elements for reliable event definition.
<button data-heap-id="add-to-cart" class="css-1a2b3c">Add to Cart</button>
Data volume and cost: Auto-capture generates significantly more data than manual instrumentation. Heap pricing scales with Monthly Tracked Users (MTUs). For high-traffic marketing sites, consider installing Heap only on authenticated sections of the application where user-level tracking is most valuable.
Retroactive analysis limitations: Retroactive event definitions work on the raw interaction data Heap captured. If an element was not in the DOM when Heap's auto-capture ran (e.g., content rendered in an iframe, Shadow DOM, or canvas), there is no raw data to retroactively define events against. Supplement with heap.track() calls for interactions that auto-capture cannot observe.
Cross-domain tracking: Heap identifies users per-domain by default. To track a user across multiple domains, pass the Heap user ID between domains via URL parameter and call heap.identify() on both domains with the same identity string.
Heap and Content Security Policy: If your CSP blocks inline scripts, load Heap via a nonce:
<script nonce="YOUR_NONCE" type="text/javascript">
// Heap snippet here
</script>
Ensure https://cdn.heapanalytics.com and https://heapanalytics.com are in script-src and connect-src respectively.