Webhooks
When a visitor is identified, Bullseye sends a JSON identification payload with visitor, company, session, and page-view data. This page documents every field.
Where the payload is delivered
| Channel | Plan | Body | Signed | Setup |
|---|---|---|---|---|
| Custom webhook automation | Every plan | Wrapped: { "event": ..., "payload": <identification payload> } | No (Bearer token / custom header) | Webhooks integration |
| Partner identification webhook | Enterprise | The identification payload, unwrapped | Yes, X-Bullseye-Signature | Partner API webhooks |
Both use the same snake_case payload documented below.
Event Types
| Event | Description |
|---|---|
visitor.identified | Fired the first time a visitor session is identified on your website |
visitor.phone_revealed | Fired when a phone number is unlocked for a visitor (manual or auto enrichment). Same payload shape as visitor.identified with visitor.phone populated. Partner webhooks only. Initial identify events often have an empty phone; listen for this event or poll the visitor profile. |
Payload Structure
Every field is present in every delivery unless marked optional. Missing text fields are empty strings (""), not null. Only visitor.homeowner, visitor.married, and visitor.children can be null. Session device, IP, and engagement fields are omitted when empty.
Top-Level Fields
| Field | Type | Description |
|---|---|---|
event_type | string | Event type (e.g., visitor.identified) |
timestamp | string | ISO 8601 timestamp when the event occurred |
organization_id | string | Your Bullseye organization ID (UUID) |
external_id | string | Optional. Partner-provided external ID for the sub-organization. Omitted when unset |
session_id | string | Unique identifier for the visitor session (UUID) |
workspace | string | Organization name |
visitor | object | Identified visitor profile data |
company | object | Company data associated with the visitor |
session | object | Session metadata (attribution, device, geo, engagement) |
page_views | array | Pages visited during this session (url, visited_at) |
Visitor Object
| Field | Description |
|---|---|
id | Bullseye visitor UUID |
email | Primary email (work email preferred) |
personal_email | Personal email, if different |
first_name, last_name, job_title | Name and role |
phone, location, photo_url | Contact and profile |
linkedin_url, facebook_url, twitter_url | Social profiles |
address, address_2, city, state, postal_code | Mailing address |
gender, age_range, income_range, net_worth | Demographics (when available) |
homeowner, married, children | Demographic booleans (nullable) |
icp | Whether the visitor matches any ICP |
icp_statuses | Map of ICP slug → match status |
persona_match | Whether the visitor matches persona filters |
created_at, last_seen_at | First identified and most recent identification timestamps |
Company Object
| Field | Description |
|---|---|
id | Bullseye company UUID |
name, domain, industry | Company identity |
size | Employee count (string) |
employee_size_range, revenue_range | Firmographic ranges |
linkedin_url, website_url, description, logo_url | Company profile |
address, address_2, city, region, postal_code | Company address |
Session Object
| Field | Description |
|---|---|
created_at | When the session started |
landing_page, entry_page, exit_page | Page navigation |
referrer, device_type, first_visit | Visit context |
utm_source, utm_campaign, utm_medium, utm_term, utm_content | UTM parameters |
campaign_name, campaign_source, campaign_medium, campaign_term, campaign_content | Campaign tracking |
search_term, search_engine | Search attribution |
country, region, city | Geo from reverse-IP lookup when available; may be empty on older sessions |
ip_address | Visitor IP (omitted when IP tracking is disabled) |
user_agent, browser, browser_version, os, os_version, device_model | Device/browser |
timezone, language, screen_width, screen_height | Client signals (timezone comes from the browser or reverse-IP lookup) |
isp | ISP / network name from reverse-IP lookup |
asn | Autonomous System Number (e.g. AS7018) |
connection_type | Network type: isp, hosting, business, education, etc. |
is_hosting | true for datacenter / hosting / cloud egress IPs (always present; false is meaningful) |
is_anonymous | true for VPN, proxy, Tor, or relay traffic (always present; false is meaningful) |
session_duration_seconds, max_scroll_percent, page_view_count | Engagement metrics |
tracking_params | Ad platform params (gclid, fbclid, li_fat_id, etc.) |
IP Intelligence
Session IP fields are populated during /track by a reverse-IP lookup. They appear on custom webhook automations, Partner identification webhooks, and the Main API visitor profile (GET visitor profile → sessions[]).
| Field | Webhook (session.*) | Main API (SessionProfile) | Segmentation |
|---|---|---|---|
| Country / region / city | country, region, city | country, region, city | - |
| ISP / ASN | isp, asn | isp, asn | - |
| Connection type | connection_type | connectionType | - |
| Datacenter flag | is_hosting | isHosting | is_hosting |
| VPN/proxy flag | is_anonymous | isAnonymous | is_anonymous |
| Visitor IP | ip_address (if IP tracking enabled) | ipAddress | - |
| Postal code, lat/long | Not exposed | Not exposed | Internal cache only |
Fields are best-effort: lookup failures leave strings empty and booleans at false. City and region may be empty for some IPs.
Example Payload (full)
{
"event_type": "visitor.identified",
"timestamp": "2025-01-15T10:30:00Z",
"organization_id": "550e8400-e29b-41d4-a716-446655440000",
"session_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"workspace": "Acme Corp",
"visitor": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "jane.smith@acme.com",
"personal_email": "jane.doe@gmail.com",
"first_name": "Jane",
"last_name": "Smith",
"job_title": "VP of Marketing",
"phone": "+14155551234",
"location": "San Francisco, CA",
"photo_url": "https://example.com/photo.jpg",
"linkedin_url": "https://linkedin.com/in/janesmith",
"facebook_url": "",
"twitter_url": "",
"address": "123 Market St",
"address_2": "",
"city": "San Francisco",
"state": "CA",
"postal_code": "94102",
"gender": "",
"age_range": "",
"income_range": "",
"net_worth": "",
"homeowner": null,
"married": null,
"children": null,
"icp": true,
"icp_statuses": { "enterprise-saas": true },
"persona_match": true,
"created_at": "2025-01-10T08:00:00Z",
"last_seen_at": "2025-01-15T10:30:00Z"
},
"company": {
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"name": "Acme Corp",
"domain": "acme.com",
"industry": "Technology",
"size": "500",
"employee_size_range": "101-500",
"revenue_range": "$10M-$50M",
"linkedin_url": "https://linkedin.com/company/acme",
"website_url": "https://acme.com",
"description": "Enterprise software company",
"logo_url": "https://example.com/acme-logo.png",
"address": "456 Business Ave",
"address_2": "",
"city": "Austin",
"region": "Texas",
"postal_code": "78701"
},
"session": {
"created_at": "2025-01-15T10:25:00Z",
"landing_page": "https://acme.com/pricing",
"entry_page": "https://acme.com",
"exit_page": "https://acme.com/contact",
"referrer": "https://google.com",
"device_type": "desktop",
"first_visit": false,
"utm_source": "google",
"utm_campaign": "brand",
"utm_medium": "cpc",
"utm_term": "visitor identification",
"utm_content": "banner",
"campaign_name": "",
"campaign_source": "",
"campaign_medium": "",
"campaign_term": "",
"campaign_content": "",
"search_term": "visitor identification software",
"search_engine": "Google",
"country": "US",
"region": "Florida",
"city": "Boynton Beach",
"ip_address": "168.196.91.87",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
"browser": "Chrome",
"browser_version": "120.0.0.0",
"os": "Mac OS X",
"os_version": "10.15.7",
"device_model": "",
"timezone": "America/New_York",
"language": "en-US",
"screen_width": 1920,
"screen_height": 1080,
"isp": "AT&T Enterprises, LLC",
"asn": "AS7018",
"connection_type": "isp",
"is_hosting": false,
"is_anonymous": false,
"session_duration_seconds": 312,
"max_scroll_percent": 85,
"page_view_count": 3,
"tracking_params": {
"gclid": "abc123"
}
},
"page_views": [
{
"url": "https://acme.com/pricing",
"visited_at": "2025-01-15T10:25:00Z"
},
{
"url": "https://acme.com/features",
"visited_at": "2025-01-15T10:28:00Z"
},
{
"url": "https://acme.com/contact",
"visited_at": "2025-01-15T10:29:30Z"
}
]
}Partner identification webhooks send this object as the request body. Custom webhook automations send it under payload: { "event": ..., "payload": { ... } }. See Webhooks integration.
Signature Verification
Partner identification webhooks only. Custom webhook automations are not signed; authenticate them with a Bearer token or custom header instead.
Partner deliveries include an X-Bullseye-Signature header: an HMAC-SHA256 signature of the raw request body using your webhook secret. They also include X-Bullseye-Timestamp (RFC 3339, UTC), X-Bullseye-Organization-Id, and X-Bullseye-External-Id (when set). Those headers are not part of the signature.
Verification Steps
- Read the raw request body (before any JSON parsing)
- Compute
HMAC-SHA256(raw_body, your_webhook_secret) - Encode the result as hexadecimal
- Compare with the value in
X-Bullseye-Signature
If the values match, the webhook is authentic. Reject requests where the signature does not match.
Example (Pseudocode)
signature = request.headers["X-Bullseye-Signature"]
expected = hmac_sha256_hex(request.raw_body, webhook_secret)
if signature != expected:
return 401 # UnauthorizedDelivery Behavior
- One attempt per event. Deliveries are not retried automatically
- Timeout: partner webhooks time out after 30 seconds. A
4xx/5xxresponse or timeout is logged as failed - Failures: partner deliveries show in the Partner API webhook logs; custom webhook automations show in the automation’s run history
- Duplicates: design for them anyway (manual re-runs, replays). Deduplicate on
session_idplusevent_type
Best Practices
- Use HTTPS - Webhook endpoints must use HTTPS
- Authenticate requests - Verify
X-Bullseye-Signatureon partner webhooks; check your Bearer token or secret header on custom webhook automations - Respond quickly - Return 200 immediately and process the payload asynchronously if needed
- Handle duplicates - Use
session_idor a combination of fields to deduplicate events - Log failures - Monitor for 4xx/5xx responses and fix endpoint issues promptly