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

ChannelPlanBodySignedSetup
Custom webhook automationEvery planWrapped: { "event": ..., "payload": <identification payload> }No (Bearer token / custom header)Webhooks integration
Partner identification webhookEnterpriseThe identification payload, unwrappedYes, X-Bullseye-SignaturePartner API webhooks

Both use the same snake_case payload documented below.

Event Types

EventDescription
visitor.identifiedFired the first time a visitor session is identified on your website
visitor.phone_revealedFired 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

FieldTypeDescription
event_typestringEvent type (e.g., visitor.identified)
timestampstringISO 8601 timestamp when the event occurred
organization_idstringYour Bullseye organization ID (UUID)
external_idstringOptional. Partner-provided external ID for the sub-organization. Omitted when unset
session_idstringUnique identifier for the visitor session (UUID)
workspacestringOrganization name
visitorobjectIdentified visitor profile data
companyobjectCompany data associated with the visitor
sessionobjectSession metadata (attribution, device, geo, engagement)
page_viewsarrayPages visited during this session (url, visited_at)

Visitor Object

FieldDescription
idBullseye visitor UUID
emailPrimary email (work email preferred)
personal_emailPersonal email, if different
first_name, last_name, job_titleName and role
phone, location, photo_urlContact and profile
linkedin_url, facebook_url, twitter_urlSocial profiles
address, address_2, city, state, postal_codeMailing address
gender, age_range, income_range, net_worthDemographics (when available)
homeowner, married, childrenDemographic booleans (nullable)
icpWhether the visitor matches any ICP
icp_statusesMap of ICP slug → match status
persona_matchWhether the visitor matches persona filters
created_at, last_seen_atFirst identified and most recent identification timestamps

Company Object

FieldDescription
idBullseye company UUID
name, domain, industryCompany identity
sizeEmployee count (string)
employee_size_range, revenue_rangeFirmographic ranges
linkedin_url, website_url, description, logo_urlCompany profile
address, address_2, city, region, postal_codeCompany address

Session Object

FieldDescription
created_atWhen the session started
landing_page, entry_page, exit_pagePage navigation
referrer, device_type, first_visitVisit context
utm_source, utm_campaign, utm_medium, utm_term, utm_contentUTM parameters
campaign_name, campaign_source, campaign_medium, campaign_term, campaign_contentCampaign tracking
search_term, search_engineSearch attribution
country, region, cityGeo from reverse-IP lookup when available; may be empty on older sessions
ip_addressVisitor IP (omitted when IP tracking is disabled)
user_agent, browser, browser_version, os, os_version, device_modelDevice/browser
timezone, language, screen_width, screen_heightClient signals (timezone comes from the browser or reverse-IP lookup)
ispISP / network name from reverse-IP lookup
asnAutonomous System Number (e.g. AS7018)
connection_typeNetwork type: isp, hosting, business, education, etc.
is_hostingtrue for datacenter / hosting / cloud egress IPs (always present; false is meaningful)
is_anonymoustrue for VPN, proxy, Tor, or relay traffic (always present; false is meaningful)
session_duration_seconds, max_scroll_percent, page_view_countEngagement metrics
tracking_paramsAd 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[]).

FieldWebhook (session.*)Main API (SessionProfile)Segmentation
Country / region / citycountry, region, citycountry, region, city-
ISP / ASNisp, asnisp, asn-
Connection typeconnection_typeconnectionType-
Datacenter flagis_hostingisHostingis_hosting
VPN/proxy flagis_anonymousisAnonymousis_anonymous
Visitor IPip_address (if IP tracking enabled)ipAddress-
Postal code, lat/longNot exposedNot exposedInternal 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

  1. Read the raw request body (before any JSON parsing)
  2. Compute HMAC-SHA256(raw_body, your_webhook_secret)
  3. Encode the result as hexadecimal
  4. 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  # Unauthorized

Delivery Behavior

  • One attempt per event. Deliveries are not retried automatically
  • Timeout: partner webhooks time out after 30 seconds. A 4xx/5xx response 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_id plus event_type

Best Practices

  • Use HTTPS - Webhook endpoints must use HTTPS
  • Authenticate requests - Verify X-Bullseye-Signature on 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_id or a combination of fields to deduplicate events
  • Log failures - Monitor for 4xx/5xx responses and fix endpoint issues promptly