Pixel and DSP Events API
Overview
The Events API lets you query pixel events from your website in real-time. When a visitor hits a page with your pixel installed, the platform captures the event and, if the visitor can be identified, enriches it with contact and company data.
flowchart LR
A[Create Pixel] --> B[Install on Site]
B --> C[Query Events]
C --> D[Filter and Deduplicate]
D --> E[Export or Integrate]
style A fill:#3b82f6,color:#fff
style B fill:#3b82f6,color:#fff
style C fill:#3b82f6,color:#fff
style D fill:#3b82f6,color:#fff
style E fill:#22c55e,color:#fff
Base URL: https://api.delivr.ai
Prerequisites:
- An org API key + secret pair from Authentication
- A project and pixel from Account Setup
Authentication: organization API key + secret. Both headers are required.
X-Api-Key: YOUR_API_KEY
X-Api-Secret: YOUR_API_SECRET
Create a pair at https://app.delivr.ai/{org_id}/settings/api-keys. The key is scoped to your organization, so resolved (identity-enriched) data flows back automatically. You do not need a separate org header.
Step 1: Create a Pixel
Pixels track visitor activity on your website. The fastest path is the dashboard: open your project, go to Settings > Pixels, and click Create pixel.
If you'd rather create the pixel programmatically, use the Partner API with the client_id / client_secret from an app (see Account Setup for how to create an app):
curl -X POST https://api.delivr.ai/public/core/api/pixel/create \
-H "Authorization: Bearer YOUR_PLATFORM_JWT" \
-H "X-Delivr-Client-ID: YOUR_CLIENT_ID" \
-H "X-Delivr-Client-Secret: YOUR_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-H "project_id: YOUR_PROJECT_ID" \
-d '{
"title": "My Website Pixel"
}'Pixel creation lives on the Partner API (
api.delivr.ai) and uses app credentials, which are separate from the organization API key + secret used for event queries below.
Response
{
"response": {
"pixel_id": "8a755c42-b2b1-...",
"installation_url": "https://cdn.delivr.ai/pixels/8a755c42-b2b1-.../p.js"
}
}Install the Pixel
Add this script tag to your website's <head>:
<script id="delivr-ai" src="https://cdn.delivr.ai/pixels/YOUR_PIXEL_ID/p.js" async></script>Or install via Google Tag Manager.
Connect Your Consent Manager
Availability: The consent-aware SDK is not yet deployed to the
p.jsscripts currently served from the CDN. Do not adddata-consent-categoryor rely onsetConsentin production until the consent-aware pixel release is available for your pixel. The integration below documents that release.
Your consent management platform (CMP) should remain the source of truth for whether marketing collection is allowed. Add data-consent-category to opt the pixel into consent-aware mode:
<script
id="delivr-ai"
src="https://cdn.delivr.ai/pixels/YOUR_PIXEL_ID/p.js"
data-consent-category="marketing"
async
></script>Publish the CMP's current decision before the SDK loads, then send every later change to the runtime API:
function setDelivrMarketingConsent(granted) {
window.__delivrPixelConsent = granted === true;
window.PixelSDK?.setConsent(granted === true);
}
// Call once with the CMP's initial regional decision, then again on changes.
setDelivrMarketingConsent(marketingAllowed);The pixel does not inspect your CMP, banner, or Global Privacy Control (GPC) signal directly. Your integration must subscribe to the CMP's initial decision and every later change, then call the function above.
The CMP decides the value using its regional policy. In a typical US implied-consent or opt-out configuration, it may return true without requiring a banner click. If the visitor opts out, or if your CMP maps a recognized GPC signal to denied marketing consent, pass false. In an express-consent region, pass false until the CMP reports a grant.
When consent is false, the SDK does not initialize or transmit pixel events or identity-enrichment requests. Calling setConsent(false) also stops those transmissions after the SDK has initialized. Calling setConsent(true) activates an SDK that was waiting. Without data-consent-category, the pixel starts immediately and setConsent does not gate transmissions.
Verify the Installation
After installing the script, confirm the setup with the verification endpoint:
curl "https://api.delivr.ai/public/core/api/pixel/verify/YOUR_PIXEL_ID?url=https%3A%2F%2Fwww.yoursite.com%2F" \
-H "Authorization: Bearer YOUR_PLATFORM_JWT" \
-H "X-Delivr-Client-ID: YOUR_CLIENT_ID" \
-H "X-Delivr-Client-Secret: YOUR_CLIENT_SECRET" \
-H "project_id: YOUR_PROJECT_ID"{
"response": {
"pixel_id": "YOUR_PIXEL_ID",
"verified": true,
"events_last_24h": 0,
"window_start": "2026-08-19T17:00:00Z",
"window_end": "2026-08-20T17:00:00Z",
"message": "Pixel script is present on the page. No events recorded yet; they appear within a minute of a page view.",
"page": {
"url": "https://www.yoursite.com/",
"status": "found"
}
}
}Two signals are checked, and either verifies:
- Page presence (
page.status): we fetch the page named by the required?url=parameter and confirm the install script is in its HTML. This works immediately after install, before any visitor traffic. Caveats: tag-manager installs (for example Google Tag Manager) inject the script at runtime and are not detectable this way, andfetch_failedmeans the page could not be checked (site down or bot protection), not that the pixel is missing. - Recent events (
events_last_24h): events are queryable within seconds of a page view, so loading a page that includes the pixel and re-checking also verifies, and is the fallback for tag-manager installs.
The same check is available on the organization API key at GET /api/v1/pixel/verify/{pixel_id}?project_id=YOUR_PROJECT_ID&url=....
Find Your Pixel ID
If you already have a pixel installed, find the ID from:
- Dashboard: Settings > Pixels > Copy the pixel ID
- Your pixel script: the ID is in the URL:
https://cdn.delivr.ai/pixels/YOUR_PIXEL_ID/p.js
Step 2: Query Events
Building a new integration? Use the Events API v3 tier routes instead. The route you call fixes both the billing tier and the identity fields returned, so the cost of a call is legible from the URL. The v2 endpoints below keep working and take the same query parameters, so the filtering, field selection, and time-window sections of this page apply to both.
Request
GET /api/v3/events/resolution_full_profile?source=pixel&pixel_id=YOUR_PIXEL_ID&start_ms=START_MS&end_ms=END_MS&limit=20&offset=0
Required Parameters
| Parameter | Type | Description |
|---|---|---|
source | string | Event source. Use pixel for on-domain events. |
pixel_id or project_id | string (UUID) | Your pixel ID (from dashboard Settings > Pixels) or your project ID. Provide one or the other. |
start_ms | integer | Start of time window in milliseconds since epoch |
end_ms | integer | End of time window in milliseconds since epoch |
You can use either
pixel_idorproject_idto query events. If a project has multiple pixels, querying byproject_idreturns events from all of them.
Optional Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 100 | Max rows to return |
offset | integer | 0 | Skip this many rows (for pagination) |
filter | string | Filter expression (see Filters section) | |
select | string | Comma-separated field names to return | |
distinct | string | Deduplicate by field: hem or company_domain | |
has_valuable_data | boolean | Only return rows with actionable contact or company data | |
orderby | string | Sort field and direction: field:asc or field:desc |
Time Window Limit
Each request can span a maximum of 25 hours. For longer ranges, split into multiple requests with consecutive time windows.
Generating Timestamps
import time
# Current time
end_ms = int(time.time() * 1000)
# 24 hours ago
start_ms = end_ms - (24 * 60 * 60 * 1000)
# Specific date (2026-01-28 00:00 UTC)
import datetime
dt = datetime.datetime(2026, 1, 28, tzinfo=datetime.timezone.utc)
start_ms = int(dt.timestamp() * 1000)Example Response (200 OK)
Resolved event (visitor was identified):
{
"pixel_id": "YOUR_PIXEL_ID",
"start_ms": 1769558400000,
"end_ms": 1769644800000,
"limit": 20,
"offset": 0,
"rows": [
{
"event_id": "8911fff2-c21e-42c3-85ae-cd31ae79de96",
"event_type": "page_view",
"timestamp": "2026-01-28T01:58:12.557202Z",
"ts_millis": 1769565492557,
"pixel_id": "YOUR_PIXEL_ID",
"project_id": "YOUR_PROJECT_ID",
"cookie_id": 3429795123691521,
"resolved": true,
"first_name": "jane",
"last_name": "smith",
"job_title": "product analyst",
"department": "information technology",
"seniority_level": "staff",
"company_name": "acme corp",
"company_domain": "acmecorp.com",
"company_industry": "government administration",
"company_employee_count_range": "10000+",
"current_business_email": "[email protected]",
"current_business_email_validation_status": "valid",
"linkedin_url": "https://linkedin.com/in/jane-smith-12345678",
"email": "[email protected]",
"referrer_url": "https://www.yoursite.com/",
"event_data": "{\"url\":\"https://www.yoursite.com/pricing\",\"title\":\"Pricing Page\"}",
"client_ip": "88.98.217.66",
"user_agent": "Mozilla/5.0 ...",
"...": "~97 total fields when resolved"
}
],
"meta": {
"files_scanned": 29,
"buckets_scanned": 1,
"took_ms": 151
},
"index_name": "project_id"
}Unresolved event (visitor was not identified):
{
"rows": [
{
"event_id": "1542601a-3eca-48d1-81af-9a770714ac26",
"event_type": "page_view",
"timestamp": "2026-01-28T00:37:48.768321Z",
"ts_millis": 1769560668768,
"pixel_id": "YOUR_PIXEL_ID",
"project_id": "YOUR_PROJECT_ID",
"cookie_id": 109879807197643124,
"client_ip": "92.50.23.199",
"ip": "34.8.133.12",
"referrer_url": "https://www.yoursite.com/",
"event_data": "{\"url\":\"https://www.yoursite.com/pricing\",\"title\":\"Pricing\"}",
"headers": "{...}",
"user_agent": "Mozilla/5.0 ..."
}
]
}Unresolved events contain ~13 fields (raw event data only). Resolved events contain ~97 fields (enriched with contact, company, and demographic data).
Step 3: Filter Events
Filters use the format field:operator:value, with multiple filters separated by commas (AND logic).
Filter Operators
| Operator | Example | Description |
|---|---|---|
eq | resolved:eq:true | Exact match |
ne | current_business_email:ne: | Not equal. Use an empty value to exclude null/empty fields. |
gt | ts_millis:gt:1778673000000 | Greater than (numeric fields only) |
gte | ts_millis:gte:1778673000000 | Greater than or equal (numeric fields only) |
lt | ts_millis:lt:1778673000000 | Less than (numeric fields only) |
lte | ts_millis:lte:1778673000000 | Less than or equal (numeric fields only) |
Unknown or unsupported operators are silently ignored, so a typo returns unfiltered results. Verify each filter expression against
/api/v3/events/web_eventwith a smalllimitbefore depending on it: that tier carries no identity fields, so it is the cheapest way to see whether a filter is actually matching. To exclude null or empty values for a field, usene:with an empty value (for example,current_business_email:ne:).
Common Filter Examples
# Only resolved (identified) visitors
filter=resolved:eq:true
# Only page views
filter=event_type:eq:page_view
# Resolved visitors with a non-empty company name
filter=resolved:eq:true,company_name:ne:
# Resolved visitors after a specific timestamp (epoch ms)
filter=resolved:eq:true,ts_millis:gte:1778673000000
# Specific event type + resolved
filter=resolved:eq:true,event_type:eq:page_view
Filter to Non-Empty Business Emails
To return only resolved visitors whose contact record carries a business email:
filter=resolved:eq:true,current_business_email:ne:
For a stricter list (only emails that passed third-party validation, excluding catchall and unknown):
filter=resolved:eq:true,current_business_email_validation_status:eq:valid
current_business_email_validation_status takes three values: valid, catchall, unknown. Pair :eq:valid with current_business_email:ne: for belt-and-suspenders.
Filter in a Request
GET /api/v3/events/resolution_full_profile?source=pixel&pixel_id=YOUR_PIXEL_ID&start_ms=START_MS&end_ms=END_MS&limit=20&filter=resolved%3Aeq%3Atrue
URL-encode the filter value.
:becomes%3A, soresolved:eq:truebecomesresolved%3Aeq%3Atrue.
Step 4: Deduplicate Results
Use distinct and has_valuable_data together to get one row per person or company.
One Row Per Person
GET /api/v3/events/resolution_full_profile?source=pixel&pixel_id=...&start_ms=...&end_ms=...&distinct=hem&has_valuable_data=true&limit=50
Returns one row per unique identity (HEM). Only includes rows where:
- The visitor has a displayable email, OR
- The visitor has a name AND company
One Row Per Company
GET /api/v3/events/resolution_full_profile?source=pixel&pixel_id=...&start_ms=...&end_ms=...&distinct=company_domain&has_valuable_data=true&limit=50
Returns one row per unique company domain. Only includes rows where the company name is present.
Field Projection
Use the select parameter to return only specific fields. This reduces response size.
Fields that are null for a given row are omitted from the response. Your code should handle missing keys gracefully.
GET /api/v3/events/resolution_full_profile?source=pixel&pixel_id=...&start_ms=...&end_ms=...&select=event_type,referrer_url,event_data&limit=5
Example Response
{
"rows": [
{
"event_type": "page_view",
"referrer_url": "https://www.yoursite.com/",
"event_data": "{\"url\":\"https://www.yoursite.com/pricing\"}"
}
]
}Available Fields (Schema)
GET /api/v3/events_schema?source=pixel
The only required parameter is source (pixel for on-domain events). Returns the full field list for that source.
Example Response (200 OK)
{
"fields": [
{ "name": "event_id", "data_type": "Utf8", "nullable": false },
{ "name": "pixel_id", "data_type": "Utf8", "nullable": false },
{ "name": "event_type", "data_type": "Utf8", "nullable": false },
{ "name": "timestamp", "data_type": "Utf8", "nullable": false },
{ "name": "ts_millis", "data_type": "Int64", "nullable": false },
{ "name": "cookie_id", "data_type": "Int64", "nullable": true },
{ "name": "resolved", "data_type": "Boolean", "nullable": true },
{ "name": "first_name", "data_type": "Utf8", "nullable": true },
{ "name": "last_name", "data_type": "Utf8", "nullable": true },
{ "name": "company_name", "data_type": "Utf8", "nullable": true },
{ "name": "company_domain", "data_type": "Utf8", "nullable": true },
{ "name": "job_title", "data_type": "Utf8", "nullable": true },
{ "name": "email", "data_type": "Utf8", "nullable": true }
]
}This is a partial list. The full schema contains ~97 fields. Call the endpoint to see all fields.
Key Field Categories
| Category | Example Fields |
|---|---|
| Event | event_id, event_type, timestamp, ts_millis, cookie_id |
| Page | referrer_url, event_data (JSON with URL, title, viewport) |
| Identity | resolved, hem, hems, email, domain_lc |
| Person | first_name, last_name, job_title, department, seniority_level |
| Company | company_name, company_domain, company_industry, company_employee_count_range |
| Location | personal_city, personal_state, personal_country, company_city |
| Demographics | age_range, gender, income_range_lc, net_worth, is_homeowner |
| Contact | current_business_email, business_emails, personal_emails, phones, linkedin_url |
| Hashes | emails_sha256_lc_hem, emails_md5_lc_hem, emails_sha1_lc_hem |
| Network | client_ip, ip, user_agent, headers |
Pagination
The events API uses offset-based pagination. There is no total count in the response, and no endpoint returns one. Page until a request comes back short of your limit, which is the last page.
How to Paginate
- Request with
offset=0&limit=100 - If you get back 100 rows (equal to your limit), there may be more. Request
offset=100&limit=100 - If you get fewer rows than your limit, you've reached the end
Example
import requests
all_rows = []
offset = 0
limit = 100
while True:
resp = requests.get(
"https://api.delivr.ai/api/v3/events/resolution_full_profile",
params={
"source": "pixel",
"pixel_id": "YOUR_PIXEL_ID",
"start_ms": start_ms,
"end_ms": end_ms,
"limit": limit,
"offset": offset,
"filter": "resolved:eq:true",
},
headers={
"X-Api-Key": "YOUR_API_KEY",
"X-Api-Secret": "YOUR_API_SECRET",
},
)
data = resp.json()
rows = data.get("rows", [])
all_rows.extend(rows)
if len(rows) < limit:
break # No more rows
offset += limit
print(f"Total rows fetched: {len(all_rows)}")For ranges longer than 25 hours, split into 24-hour chunks and paginate within each chunk.
Complete Example
Python: Fetch Resolved Visitors from the Last 24 Hours
import time
import requests
API_KEY = "YOUR_API_KEY"
API_SECRET = "YOUR_API_SECRET"
PIXEL_ID = "YOUR_PIXEL_ID"
end_ms = int(time.time() * 1000)
start_ms = end_ms - (24 * 60 * 60 * 1000)
resp = requests.get(
"https://api.delivr.ai/api/v3/events/resolution_full_profile",
params={
"source": "pixel",
"pixel_id": PIXEL_ID,
"start_ms": start_ms,
"end_ms": end_ms,
"limit": 50,
"filter": "resolved:eq:true",
"distinct": "hem",
"has_valuable_data": "true",
},
headers={
"X-Api-Key": API_KEY,
"X-Api-Secret": API_SECRET,
},
)
data = resp.json()
rows = data.get("rows", [])
print(f"Found {len(rows)} unique identified visitors")
for row in rows:
print(f" {row.get('first_name')} {row.get('last_name')}")
print(f" {row.get('job_title')} at {row.get('company_name')}")
print(f" Email: {row.get('current_business_email')}")
print()curl: Quick Check
# Fetch 5 resolved events
curl "https://api.delivr.ai/api/v3/events/resolution_full_profile?source=pixel&pixel_id=YOUR_PIXEL_ID&start_ms=$START_MS&end_ms=$END_MS&limit=5&filter=resolved%3Aeq%3Atrue" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "X-Api-Secret: YOUR_API_SECRET"Event Types
| Event Type | Description |
|---|---|
page_view | Page was loaded |
click | Element was clicked |
form_submission | Form was submitted |
file_download | File was downloaded |
copy | Text was copied |
exit_intent | Mouse moved toward browser close/back |
user_idle | User went idle on the page |
video_play | Video started playing |
video_pause | Video was paused |
video_complete | Video finished playing |
Filter by type: filter=event_type:eq:page_view
Event Data (Custom Passthrough Values)
The event_data field contains page-level context captured by the pixel. It is also where custom passthrough values appear.
What the Pixel Captures Automatically
For standard events, the pixel sends structured data including page URL, title, viewport dimensions, and event-specific metadata:
{
"url": "https://www.yoursite.com/pricing",
"title": "Pricing - YourSite",
"timestamp": "2026-02-16T01:02:13.536Z",
"screen": { "height": 900, "width": 1440 },
"viewport": { "height": 778, "width": 1440 },
"referrer": null
}For click events, it also includes the clicked element:
{
"url": "https://www.yoursite.com/",
"timestamp": "2026-02-16T03:55:34.851Z",
"coordinates": { "x": 959, "y": 139 },
"element": {
"tag": "A",
"text": "Meet the Team",
"href": "https://www.yoursite.com/meet-the-team/"
}
}Custom Passthrough Values
You can attach custom key-value pairs to every event the pixel sends. There are two approaches depending on whether you're using the JavaScript pixel or sending events server-to-server.
Option A: HTML Attribute (Simplest)
Add data-global-params to the pixel script tag. These values are sent with every event automatically:
<script id="delivr-ai"
src="https://cdn.delivr.ai/pixels/YOUR_PIXEL_ID/p.js"
data-global-params='{"campaign_id":"summer-2026","client_uid":"your-external-id"}'
async></script>Using a single-page app (React, Next.js, Vue, etc.)? Option A on its own only fires a
page_viewfor the initial route; subsequent in-app navigations will not generate page views, anddata-global-paramsis frozen at script-load time. See the SPA Pixel Integration guide for a copy-paste snippet that fixes this without changing your app code.
Option B: JavaScript API (Dynamic)
After the pixel loads, call setGlobalParams or setEventParams on the SDK:
// Send with ALL events
PixelSDK.setGlobalParams({
campaign_id: "summer-2026",
account_tier: "premium"
});
// Send only with specific event types
PixelSDK.setEventParams("page_view", {
page_category: "pricing"
});setGlobalParams and setEventParams merge. Calling them multiple times adds to the existing params. Event-specific params override global params when the keys overlap.
The SDK object is available as both
PixelSDKandDelivrSDKon the window.
Where Custom Values Appear
Custom values are nested inside event_data.static_params. The payload the pixel sends looks like:
{
"event_type": "page_view",
"event_data": {
"url": "https://www.yoursite.com/pricing",
"title": "Pricing Page",
"timestamp": "2026-02-16T01:02:13.536Z",
"viewport": { "width": 1440, "height": 778 },
"screen": { "width": 1440, "height": 900 },
"static_params": {
"campaign_id": "summer-2026",
"client_uid": "your-external-id",
"page_category": "pricing"
}
},
"pixel_id": "YOUR_PIXEL_ID",
"organization_id": "YOUR_ORG_ID"
}When you query the Events API, event_data is returned as a JSON string. Parse it to access static_params:
import json
for row in rows:
event_data = json.loads(row.get("event_data", "{}"))
url = event_data.get("url")
static = event_data.get("static_params", {})
campaign = static.get("campaign_id")const eventData = JSON.parse(row.event_data || "{}");
const url = eventData.url;
const campaign = eventData.static_params?.campaign_id;Option C: Server-to-Server (Advanced)
If you need to send events from your backend (not the browser), POST directly to the pixel endpoint. The endpoint URL is in your pixel script. Look for the endpoint value:
curl -X POST https://YOUR_PIXEL_API_HOST/pixel/core/api/send-event \
-H "Content-Type: application/json" \
-d '{
"pixel_id": "YOUR_PIXEL_ID",
"organization_id": "YOUR_ORG_ID",
"event_type": "page_view",
"event_data": {
"url": "https://customer-site.com/pricing",
"title": "Pricing Page",
"campaign_id": "summer-2026",
"client_uid": "your-external-cookie-id"
}
}'With server-to-server, custom fields go directly in event_data (no static_params wrapper). The pixel API host varies by account. Find yours by inspecting the endpoint field in your pixel's JavaScript file.
Server-to-server events won't have browser context (viewport, cookies, user agent) unless you include them in the payload.
How event_data Appears in API Responses
event_data is returned as a JSON string, not a parsed object. You must parse it yourself:
{
"event_data": "{\"url\":\"https://customer-site.com/pricing\",\"title\":\"Pricing Page\",\"static_params\":{\"campaign_id\":\"summer-2026\",\"client_uid\":\"your-external-id\"}}"
}In CSV Exports
In CSV files, event_data appears as a quoted JSON string with escaped quotes:
event_data
"{""url"":""https://customer-site.com/pricing"",""title"":""Pricing Page"",""static_params"":{""campaign_id"":""summer-2026""}}"Errors
| HTTP Status | Error | Cause |
|---|---|---|
| 400 | "pixel_id is required" | Missing pixel_id parameter |
| 400 | "missing field start_ms" | Missing start_ms parameter |
| 400 | "Unsupported source 'X'" | source must be pixel (or dsp for DSP events) |
| 401 | "API key is required" | Missing X-Api-Key header |
| 401 | "Invalid API key" | API key not recognized |
| 401 | "API secret is required" | Key has secret_required enabled but X-Api-Secret was missing |
| 401 | "Invalid API secret" | X-Api-Secret does not match the stored hash |
| 404 | "pixel not found" | pixel_id does not belong to the authenticated organization |
Response Reference
Events Response
{
"pixel_id": "...",
"start_ms": 1769558400000,
"end_ms": 1769644800000,
"limit": 20,
"offset": 0,
"rows": [ ... ],
"meta": {
"files_scanned": 29,
"buckets_scanned": 1,
"took_ms": 151
}
}Counts Response
{
"count": 287,
"meta": {
"files_scanned": 29,
"buckets_scanned": 1,
"took_ms": 51
}
}Schema Response
{
"fields": [
{ "name": "field_name", "data_type": "Utf8", "nullable": true }
]
}Next Steps
- Export Events to CSV: paginate and export visitor data to CSV
- High-Intent Visitors: daily lead list from pricing page visitors
- Intent Audiences API: create audiences from intent topics instead of pixel data
- Taxonomy API: browse the intent topic catalog
Updated 9 days ago
