# Webhooks

Configure webhooks to receive real-time notifications when visitors are identified or when billing events occur. Webhooks use HMAC-SHA256 signatures for verification.

## Get Webhook Configuration

Retrieve the current webhook configuration for identification and billing.

**Request:**

```
GET /partner/webhooks
```

**Response:**

```json
{
  "identification": {
    "url": "https://example.com/webhooks/identify",
    "enabled": true,
    "hasSecret": true
  },
  "billing": {
    "url": "https://example.com/webhooks/billing",
    "enabled": true,
    "hasSecret": true
  }
}
```

**Example:**

```bash
curl -X GET "https://api.app.bullseye.so/api/v1/partner/webhooks" \
  -H "X-Partner-API-Key: your-api-key"
```

***

## Update Identification Webhook

Configure the webhook for visitor identification events.

**Request:**

```
PUT /partner/webhooks/identification
```

**Body:**

```json
{
  "url": "https://example.com/webhooks/identify",
  "secret": "whsec_your_webhook_secret",
  "enabled": true
}
```

| Field     | Type    | Required | Description                            |
| --------- | ------- | -------- | -------------------------------------- |
| `url`     | string  | No       | Webhook endpoint URL                   |
| `secret`  | string  | No       | Secret for HMAC signature verification |
| `enabled` | boolean | No       | Enable or disable the webhook          |

**Response:**

```json
{
  "identification": {
    "url": "https://example.com/webhooks/identify",
    "enabled": true,
    "hasSecret": true
  }
}
```

***

## Update Billing Webhook

Configure the webhook for billing events.

**Request:**

```
PUT /partner/webhooks/billing
```

**Body:**

```json
{
  "url": "https://example.com/webhooks/billing",
  "secret": "whsec_your_webhook_secret",
  "enabled": true
}
```

**Response:**

```json
{
  "billing": {
    "url": "https://example.com/webhooks/billing",
    "enabled": true,
    "hasSecret": true
  }
}
```

***

## Send Test Webhook

Send a test webhook to verify your endpoint is configured correctly.

**Request:**

```
POST /partner/webhooks/test
```

**Body:**

```json
{
  "type": "identification"
}
```

Valid types: `identification`, `billing`

**Response:**

```json
{
  "success": true,
  "message": "Test webhook sent successfully"
}
```

**Example:**

```bash
curl -X POST "https://api.app.bullseye.so/api/v1/partner/webhooks/test" \
  -H "X-Partner-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{"type": "identification"}'
```

***

## Get Webhook Logs

Retrieve webhook delivery logs for debugging and monitoring.

**Request:**

```
GET /partner/webhooks/logs
```

**Query Parameters:**

| Parameter        | Type    | Description                             |
| ---------------- | ------- | --------------------------------------- |
| `page`           | integer | Page number                             |
| `pageSize`       | integer | Items per page                          |
| `type`           | string  | Filter by `identification` or `billing` |
| `status`         | string  | Filter by delivery status               |
| `organizationId` | string  | Filter by organization                  |

**Response:**

```json
{
  "logs": [
    {
      "id": "uuid",
      "type": "identification",
      "organizationId": "uuid",
      "status": "delivered",
      "statusCode": 200,
      "attemptedAt": "2025-02-27T10:30:00Z",
      "responseTimeMs": 150
    }
  ],
  "total": 100,
  "page": 1,
  "pageSize": 20
}
```

***

## Identification Webhook Payload

When a visitor is identified, your webhook receives a POST request with the following payload:

```json
{
  "event_type": "visitor.identified",
  "timestamp": "2025-01-15T10:30:00Z",
  "organization_id": "uuid",
  "session_id": "uuid",
  "workspace": "Org Name",
  "visitor": {
    "id": "uuid",
    "email": "john@acme.com",
    "personal_email": "john.doe@example.com",
    "first_name": "John",
    "last_name": "Doe",
    "job_title": "CTO",
    "phone": "+1234567890",
    "location": "San Francisco, CA",
    "photo_url": "https://example.com/photo.jpg",
    "linkedin_url": "https://linkedin.com/in/johndoe",
    "facebook_url": null,
    "twitter_url": null,
    "address": "123 Main St",
    "city": "San Francisco",
    "state": "CA",
    "postal_code": "94102",
    "gender": null,
    "age_range": null,
    "income_range": null,
    "net_worth": null,
    "homeowner": null,
    "married": null,
    "children": null,
    "icp": "Enterprise Lead",
    "icp_statuses": {},
    "persona_match": true,
    "created_at": "2025-01-10T08:00:00Z",
    "last_seen_at": "2025-01-15T10:30:00Z"
  },
  "company": {
    "id": "uuid",
    "name": "Acme Inc",
    "domain": "acme.com",
    "industry": "Technology",
    "size": 150,
    "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/logo.png",
    "address": "456 Business Ave",
    "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": null,
    "campaign_source": null,
    "campaign_medium": null,
    "campaign_term": null,
    "campaign_content": null,
    "search_term": "visitor identification software",
    "search_engine": "Google",
    "country": "US",
    "region": "California",
    "city": "San Francisco",
    "tracking_params": {}
  },
  "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"
    }
  ]
}
```

***

## Signature Verification

Webhook requests include an `X-Bullseye-Signature` header containing an HMAC-SHA256 signature of the raw request body.

To verify the signature:

1. Compute HMAC-SHA256 of the raw request body using your webhook secret.
2. Compare the result (hex-encoded) with the value in `X-Bullseye-Signature`.

**Example (Node.js):**

```javascript
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expected, 'hex')
  );
}
```

**Example (Python):**

```python
import hmac
import hashlib

def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)
```

Always use the raw request body (before JSON parsing) for signature verification. Respond with `200 OK` to acknowledge receipt; non-2xx responses may trigger retries.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.bullseye.so/partner-api/webhooks.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
