# Webhook Events

Catalog of every event we deliver. All bodies are alphabetically key-sorted + minified on the wire — see [Webhook Signing Secrets](https://docs.clodo.ai/guides/webhook-secrets).

## search.completed

Fired when a People Search run finishes successfully (including 0-result runs).

```json
{
  "id": "dj_a1b2c3d4...",
  "event_type": "search.completed",
  "results": [
    {
      "first_name": "Mark",
      "last_name": "Wang",
      "full_name": "Mark Wang",
      "current_job_title": "Software Engineer",
      "headline": "Software Engineer",
      "location": "San Francisco, California, United States",
      "professional_url": "linkedin.com/in/mark-wang-8a8a2213",
      "company": {
        "name": "OpenAI",
        "domain": "openai.com",
        "professional_url": "linkedin.com/company/openai",
        "industry": "Research Services",
        "employee_count": 8626
      }
    }
  ],
  "total_returned": 1
}
```

Company `professional_url` is optional and may be `null`.

A 0-result outcome ships with `total_returned: 0` and `results: []`.

## search.failed

Fired when a People Search run fails before producing results.

```json
{
  "id": "dj_a1b2c3d4...",
  "event_type": "search.failed",
  "error": {"code": "error", "message": "People search failed; please retry."}
}
```

| `error.code` | `error.message` |
|---|---|
| `invalid_input` | `Invalid search query.` |
| `error` | `People search failed; please retry.` |

## deep_search.completed

Fired when a Deep Search run finishes successfully (including 0-result runs).

```json
{
  "id": "dj_a1b2c3d4...",
  "event_type": "deep_search.completed",
  "results": [
    {
      "first_name": "Patrick",
      "last_name": "Collison",
      "full_name": "Patrick Collison",
      "current_job_title": "CEO",
      "headline": "CEO at Stripe",
      "location": "San Francisco, California, United States",
      "professional_url": "linkedin.com/in/patrickcollison",
      "company": {
        "name": "Stripe",
        "domain": "stripe.com",
        "professional_url": "linkedin.com/company/stripe",
        "industry": "Financial Services",
        "employee_count": 8000
      },
      "email": "patrick@stripe.com",
      "experience": [
        {"company": "Stripe", "title": "CEO", "date_range": "2010–present"}
      ],
      "score": 92
    }
  ],
  "total_returned": 1
}
```

`email`, `headline`, `experience`, and `company.professional_url` may be empty (`""`, `[]`, or `null`) on a per-lead basis. Person and company `professional_url` values use bare-host form.

## deep_search.failed

```json
{
  "id": "dj_a1b2c3d4...",
  "event_type": "deep_search.failed",
  "error": {"code": "error", "message": "Deep search failed; please retry."}
}
```

| `error.code` | `error.message` |
|---|---|
| `invalid_input` | `Invalid deep-search query.` |
| `error` | `Deep search failed; please retry.` |

## agentic_search.completed

Fired when an Agentic Search finishes, including zero-result and partial deliveries. The payload contains `id`, `event_type: "agentic_search.completed"`, `results`, `total_returned`, `partial`, and `completion_reason`.

Each person may include professional details, company, experience, `fit_summary`, `proof_points`, `evidence`, and `tier`. See [Agentic Search](https://docs.clodo.ai/api-reference/endpoint/agentic-search) for the complete example and field descriptions.

When `partial` is `true`, inspect `completion_reason`: `budget_exhausted`, `deadline_exceeded`, or `user_cancelled`. A partial delivery can still be billable within the requested credit cap.

## agentic_search.failed

```json
{
  "id": "dj_a1b2c3d4e5f6478890abcdef123456789",
  "event_type": "agentic_search.failed",
  "error": {"code": "error", "message": "Internal error — you were not charged."}
}
```

| `error.code` | Meaning |
|---|---|
| `error` | Internal failure; no charge is retained. |
| `stale_running_1h` | Job timed out and was force-failed; the credit hold was refunded. |
| `admission_expired` | The job's billing authorization is no longer valid. Check billing and submit a new request. |
| `settlement_failed` | Billing could not settle the request. No result is delivered and no charge is retained. |

See [Async Polling](https://docs.clodo.ai/guides/async-polling) if webhook delivery is unavailable.

## phone_enrich.completed

Fired for both hits and clean misses. Branch on `phone === null` to detect a miss.

Hit:

```json
{
  "id": "dj_a1b2c3d4...",
  "event_type": "phone_enrich.completed",
  "phone": "+14155551234"
}
```

Miss:

```json
{
  "id": "dj_a1b2c3d4...",
  "event_type": "phone_enrich.completed",
  "phone": null
}
```

`phone` is E.164 normalized.

## phone_enrich.failed

Fired only on transport / pipeline errors.

```json
{
  "id": "dj_a1b2c3d4...",
  "event_type": "phone_enrich.failed",
  "error": {"code": "error", "message": "Phone enrichment failed; please retry."}
}
```

| `error.code` | `error.message` |
|---|---|
| `invalid_input` | `Invalid phone-enrichment request.` |
| `error` | `Phone enrichment failed; please retry.` |

## See also

- [Webhook Signature Verification](https://docs.clodo.ai/guides/webhook-signing) for HMAC verifier code.
- [Webhook Retry Policy](https://docs.clodo.ai/guides/webhook-retries) for retry schedule and what we treat as transient vs permanent.
- [Async Polling](https://docs.clodo.ai/guides/async-polling) for the polling-fallback envelope.
