> ## Documentation Index
> Fetch the complete documentation index at: https://docs.titanx.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook Callbacks

> OpenAPI specification for the job.contact.scored webhook callback

## Overview

This documents the `job.contact.scored` webhook callback that TitanX sends to your configured endpoint when a contact completes scoring.

<Info>
  **Related Documentation:**

  * [Webhooks Guide](/guides/webhooks/getting-started) - Implementation guide with code examples
  * [Webhook Management API](/api-reference/v2#available-endpoints) - Endpoints to create and manage webhooks
</Info>

## Webhook Specification

### POST job.contact.scored

TitanX sends this POST request to your configured webhook URL when a contact completes scoring.

#### Headers

<ParamField header="X-TitanX-Signature" type="string" required>
  HMAC-SHA256 signature (base64) of the request body for verification. Always verify this signature before processing the webhook.

  Example: `jdoe+XYZ123abc/def456GHI789==`
</ParamField>

<ParamField header="Content-Type" type="string" required>
  Always `application/json`
</ParamField>

<ParamField header="User-Agent" type="string">
  TitanX webhook user agent

  Example: `TitanX-Webhooks/1.0`
</ParamField>

#### Request Body

<ParamField body="payload" type="object" required>
  An object containing the scored Contact.

  <Expandable title="payload properties">
    <ParamField body="contact" type="object" required>
      The complete scored Contact object with all available fields.

      <Expandable title="Contact Properties">
        <ParamField body="id" type="string">
          Unique contact identifier (UUID)
        </ParamField>

        <ParamField body="userId" type="string">
          User ID who owns this contact (UUID)
        </ParamField>

        <ParamField body="jobId" type="string">
          Job ID this contact belongs to (UUID)
        </ParamField>

        <ParamField body="clientId" type="string">
          Client ID (UUID)
        </ParamField>

        <ParamField body="firstName" type="string" required>
          Contact's first name
        </ParamField>

        <ParamField body="lastName" type="string" required>
          Contact's last name
        </ParamField>

        <ParamField body="phones" type="object[]" required>
          Canonical phone representation for this contact. This **supersedes** the deprecated flat `phone`/`phone2`–`phone5` fields (and their `Status`/`Type`/`Country`/`Region` variants) and is the recommended way to read phone data. Unlike the flat fields, this array also includes enrichment phones beyond the 5-number cap.

          <Expandable title="phone properties">
            <ParamField body="number" type="string | null">
              The phone number (e.g., `"+14155551001"`)
            </ParamField>

            <ParamField body="status" type="string | null">
              Propensity status for this number. Possible values: `p1`, `p1 - h`, `p1 - ivr`, `p1 - dnc`, `p1 - sf`, `p2`, `p2 - h`, `p2 - ivr`, `p2 - sf`, `p3`, `p3 - h`, `p3 - ivr`, `p3 - sf`, `na`, `na - h`, `na - ivr`, `na - auth`, `na - dnc`
            </ParamField>

            <ParamField body="type" type="string | null">
              Phone type. Standard values: `mobile`, `direct`, `hq`, `other`. Phones discovered via enrichment use the form `TitanX Enriched Phone N` (e.g., `"TitanX Enriched Phone 1"`).
            </ParamField>

            <ParamField body="country" type="string | null">
              Country code for this number (e.g., `"US"`)
            </ParamField>

            <ParamField body="region" type="string | null">
              Region for this number. Possible values: `US`, `EMEA`, `APAC`
            </ParamField>

            <ParamField body="position" type="integer" required>
              1-indexed ordinal position of this phone within the contact.
            </ParamField>

            <ParamField body="ivrPath" type="string | null">
              IVR navigation path for reaching this number, when available.
            </ParamField>
          </Expandable>
        </ParamField>

        <ParamField body="bestPhone" type="object | null">
          The single recommended number to dial for this contact — TitanX's pick of the highest-propensity, most reachable number from `phones[]`, surfaced so you don't have to rank the array yourself. `null` when no suitable number is available.

          <Expandable title="bestPhone properties">
            <ParamField body="number" type="string" required>
              The recommended phone number (e.g., `"+14155551001"`)
            </ParamField>

            <ParamField body="status" type="string" required>
              Propensity status for this number. Possible values: `p1`, `p1 - h`, `p1 - ivr`, `p1 - dnc`, `p1 - sf`, `p2`, `p2 - h`, `p2 - ivr`, `p2 - sf`, `p3`, `p3 - h`, `p3 - ivr`, `p3 - sf`, `na`, `na - h`, `na - ivr`, `na - auth`, `na - dnc`
            </ParamField>

            <ParamField body="ivrPath" type="string | null">
              IVR navigation path for reaching this number, when available.
            </ParamField>
          </Expandable>
        </ParamField>

        <ParamField body="companyAccount" type="string" required>
          Company name
        </ParamField>

        <ParamField body="leadId" type="string">
          Lead identifier
        </ParamField>

        <ParamField body="email" type="string">
          Email address
        </ParamField>

        <ParamField body="title" type="string">
          Job title (e.g., "Chief Technology Officer", "VP of Sales")
        </ParamField>

        <ParamField body="phone" type="string" deprecated>
          Primary phone number. **Deprecated** — use the `phones[]` array instead.
        </ParamField>

        <ParamField body="phoneStatus" type="enum" deprecated>
          **Deprecated** — use `phones[].status`. Primary phone status. Possible values: `p1`, `p1 - h`, `p1 - ivr`, `p1 - dnc`, `p1 - sf`, `p2`, `p2 - h`, `p2 - ivr`, `p2 - sf`, `p3`, `p3 - h`, `p3 - ivr`, `p3 - sf`, `na`, `na - h`, `na - ivr`, `na - auth`, `na - dnc`
        </ParamField>

        <ParamField body="phoneType" type="enum" deprecated>
          **Deprecated** — use `phones[].type`. Primary phone type. Possible values: `mobile`, `direct`, `hq`, `other`
        </ParamField>

        <ParamField body="phoneCountry" type="string" deprecated>
          **Deprecated** — use `phones[].country`. Primary phone country code (e.g., "US")
        </ParamField>

        <ParamField body="phone2" type="string" deprecated>
          Second phone number. **Deprecated** — use the `phones[]` array instead.
        </ParamField>

        <ParamField body="phone2Status" type="enum" deprecated>
          **Deprecated** — use `phones[].status`. Second phone status (same values as phoneStatus)
        </ParamField>

        <ParamField body="phone2Type" type="enum" deprecated>
          **Deprecated** — use `phones[].type`. Second phone type (same values as phoneType)
        </ParamField>

        <ParamField body="phone2Country" type="string" deprecated>
          **Deprecated** — use `phones[].country`. Second phone country code
        </ParamField>

        <ParamField body="phone3" type="string" deprecated>
          Third phone number. **Deprecated** — use the `phones[]` array instead.
        </ParamField>

        <ParamField body="phone3Status" type="enum" deprecated>
          **Deprecated** — use `phones[].status`. Third phone status
        </ParamField>

        <ParamField body="phone3Type" type="enum" deprecated>
          **Deprecated** — use `phones[].type`. Third phone type
        </ParamField>

        <ParamField body="phone3Country" type="string" deprecated>
          **Deprecated** — use `phones[].country`. Third phone country code
        </ParamField>

        <ParamField body="phone4" type="string" deprecated>
          Fourth phone number. **Deprecated** — use the `phones[]` array instead.
        </ParamField>

        <ParamField body="phone4Status" type="enum" deprecated>
          **Deprecated** — use `phones[].status`. Fourth phone status
        </ParamField>

        <ParamField body="phone4Type" type="enum" deprecated>
          **Deprecated** — use `phones[].type`. Fourth phone type
        </ParamField>

        <ParamField body="phone4Country" type="string" deprecated>
          **Deprecated** — use `phones[].country`. Fourth phone country code
        </ParamField>

        <ParamField body="phone5" type="string" deprecated>
          Fifth phone number. **Deprecated** — use the `phones[]` array instead.
        </ParamField>

        <ParamField body="phone5Status" type="enum" deprecated>
          **Deprecated** — use `phones[].status`. Fifth phone status
        </ParamField>

        <ParamField body="phone5Type" type="enum" deprecated>
          **Deprecated** — use `phones[].type`. Fifth phone type
        </ParamField>

        <ParamField body="phone5Country" type="string" deprecated>
          **Deprecated** — use `phones[].country`. Fifth phone country code
        </ParamField>

        <ParamField body="phoneExt" type="string">
          Phone extension
        </ParamField>

        <ParamField body="linkedInUrl" type="string">
          Contact's LinkedIn profile URL
        </ParamField>

        <ParamField body="street" type="string">
          Street address
        </ParamField>

        <ParamField body="city" type="string">
          City
        </ParamField>

        <ParamField body="stateProvince" type="string">
          State or province
        </ParamField>

        <ParamField body="zipPostalCode" type="string">
          ZIP or postal code
        </ParamField>

        <ParamField body="country" type="string">
          Country
        </ParamField>

        <ParamField body="timeZone" type="string">
          Time zone (e.g., "America/Los\_Angeles")
        </ParamField>

        <ParamField body="dept" type="string">
          Department (e.g., "Engineering")
        </ParamField>

        <ParamField body="level" type="string">
          Seniority level (e.g., "C-Level")
        </ParamField>

        <ParamField body="jobFunction" type="string">
          Job function or role (e.g., "Engineering")
        </ParamField>

        <ParamField body="persona" type="string">
          Contact persona
        </ParamField>

        <ParamField body="website" type="string">
          Company website URL
        </ParamField>

        <ParamField body="companyLinkedInUrl" type="string">
          Company LinkedIn URL
        </ParamField>

        <ParamField body="companyAddress" type="string">
          Company street address
        </ParamField>

        <ParamField body="companyCity" type="string">
          Company city (e.g., "San Francisco")
        </ParamField>

        <ParamField body="companyState" type="string">
          Company state (e.g., "CA")
        </ParamField>

        <ParamField body="companyZip" type="string">
          Company ZIP code
        </ParamField>

        <ParamField body="companyCountry" type="string">
          Company country (e.g., "USA")
        </ParamField>

        <ParamField body="numberOfEmployees" type="string">
          Employee count range (e.g., "501-1000")
        </ParamField>

        <ParamField body="annualRevenue" type="string">
          Revenue range (e.g., "$50M-$100M")
        </ParamField>

        <ParamField body="industry" type="string">
          Industry category (e.g., "Technology")
        </ParamField>

        <ParamField body="primarySubIndustry" type="string">
          Sub-industry (e.g., "Software")
        </ParamField>

        <ParamField body="sicCodes" type="string">
          SIC codes (e.g., "7372")
        </ParamField>

        <ParamField body="naicsCodes" type="string">
          NAICS codes (e.g., "511210")
        </ParamField>

        <ParamField body="technologies" type="string">
          Technologies used (e.g., "AWS, React, Node.js")
        </ParamField>

        <ParamField body="leadStatus" type="enum">
          Overall lead score based on highest phone score. Possible values: `P1`, `P2`, `P3`, `Needs Attention`, `No Numbers Found`, `Uncontacted`
        </ParamField>

        <ParamField body="campaignListName" type="string">
          Campaign list name (e.g., "Q1 2024 Campaign")
        </ParamField>

        <ParamField body="recordOwner" type="string">
          Record owner (e.g., "[sales@acme.com](mailto:sales@acme.com)")
        </ParamField>

        <ParamField body="contactCrmId" type="string">
          Contact CRM identifier (e.g., "SF-12345")
        </ParamField>

        <ParamField body="accountCrmId" type="string">
          Account CRM identifier (e.g., "SF-ACC-678")
        </ParamField>

        <ParamField body="databaseContactId" type="string">
          Database contact ID
        </ParamField>

        <ParamField body="databaseCompanyId" type="string">
          Database company ID
        </ParamField>

        <ParamField body="customerContactId" type="string">
          Customer contact identifier
        </ParamField>

        <ParamField body="enrichmentContactId" type="string">
          Enrichment contact ID
        </ParamField>

        <ParamField body="intent" type="string">
          Contact intent indicator (nullable)
        </ParamField>

        <ParamField body="unmappedFields" type="object">
          Additional unmapped fields as key-value pairs
        </ParamField>

        <ParamField body="createdAt" type="string">
          Timestamp when the contact was created (ISO 8601 format)

          Example: `"2024-01-15T10:30:00Z"`
        </ParamField>

        <ParamField body="updatedAt" type="string">
          Timestamp when the contact was last updated (ISO 8601 format)

          Example: `"2024-01-15T10:35:00Z"`
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="job" type="object" required>
      Context about the job that triggered this event.

      <Expandable title="job properties">
        <ParamField body="id" type="string | null">
          The ID of the originating job (UUID). Use this to correlate the event back to a specific job. May be `null` for legacy events.
        </ParamField>

        <ParamField body="type" type="enum | null">
          The type of job that produced this event. Use this to route handling logic, as different job types annotate different fields. Possible values: `scoring`, `enrichment`, `rescoring`, `score_enriched`, `enrich_rejected`, `enrich_final`, `titan_scoring`, `lightning_scoring`, `enrich_and_score`, `enrich_and_titan`, `enrich_score_rejected`, `enrich_titan_rejected`. May be `null` for legacy events.
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="eventType" type="string" required>
  The type of webhook event. Always `"job.contact.scored"` for this callback.
</ParamField>

<ParamField body="timestamp" type="number" required>
  Unix timestamp in milliseconds when the event occurred.

  Example: `1705318200000`
</ParamField>

<ParamField body="apiVersion" type="string" required>
  API version for this webhook payload. Currently `"v2"`.
</ParamField>

<ParamField body="id" type="string" required>
  Unique identifier for this webhook event (UUID format). Use this for idempotency to detect and skip duplicate deliveries.

  Example: `"7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d"`
</ParamField>

### Expected Response

Your webhook endpoint should return appropriate HTTP status codes:

| Status Code | Description                               |
| ----------- | ----------------------------------------- |
| `200-299`   | Success - webhook processed, no retry     |
| `400-499`   | Client error - no retry (except 408, 429) |
| `408`       | Request timeout - will retry              |
| `429`       | Rate limited - will retry                 |
| `500-599`   | Server error - will retry                 |

### Retry Policy

Failed deliveries are retried with exponential backoff for up to 3 days.

## Security Requirements

### Signature Verification

All webhook requests include an `X-TitanX-Signature` header with an HMAC-SHA256 signature.

**Algorithm**: `Base64(HMAC-SHA256(webhook_secret, request_body))`

<Warning>
  Always verify the signature before processing webhook data. See the [Webhooks Guide](/guides/webhooks/getting-started#security-verifying-webhook-signatures) for implementation examples in Node.js and Python.
</Warning>

## Example Webhook Payload

```json theme={null}
{
  "payload": {
    "contact": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "userId": "abc123-def456-ghi789",
      "jobId": "job-uuid-here",
      "clientId": "client-uuid-here",
      "firstName": "John",
      "lastName": "Doe",
      "email": "john.doe@example.com",
      "phones": [
        {
          "number": "+15551234567",
          "status": "p1 - ivr",
          "type": "hq",
          "country": "US",
          "region": "US",
          "position": 1,
          "ivrPath": "#4021"
        },
        {
          "number": "+15559876543",
          "status": "p2",
          "type": "mobile",
          "country": "US",
          "region": "US",
          "position": 2,
          "ivrPath": null
        },
        {
          "number": "+15552223333",
          "status": "p3",
          "type": "TitanX Enriched Phone 1",
          "country": "US",
          "region": "US",
          "position": 3,
          "ivrPath": null
        }
      ],
      "bestPhone": {
        "number": "+15551234567",
        "status": "p1 - ivr",
        "ivrPath": "#4021"
      },
      "title": "VP of Sales",
      "companyAccount": "Acme Corp",
      "website": "https://acme.com",
      "linkedInUrl": "https://linkedin.com/in/johndoe",
      "companyLinkedInUrl": "https://linkedin.com/company/acme",
      "city": "San Francisco",
      "stateProvince": "CA",
      "country": "USA",
      "companyCity": "San Francisco",
      "companyState": "CA",
      "companyCountry": "USA",
      "numberOfEmployees": "501-1000",
      "annualRevenue": "$50M-$100M",
      "industry": "Technology",
      "leadStatus": "P1",
      "createdAt": "2024-01-15T10:30:00Z",
      "updatedAt": "2024-01-15T10:35:00Z"
    },
    "job": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "type": "scoring"
    }
  },
  "eventType": "job.contact.scored",
  "timestamp": 1705318200000,
  "apiVersion": "v2",
  "id": "7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d"
}
```

<Note>
  The flat `phone`/`phone2`–`phone5` fields (and their `Status`/`Type`/`Country`/`Region` variants) are still present in the payload for backward compatibility but are **deprecated**. New integrations should read phone data from the `phones[]` array, which is canonical and also surfaces enrichment phones beyond the five-number cap.
</Note>

## OpenAPI Definition

The complete OpenAPI 3.1 specification for this webhook callback is available in the [openapi.json](/api-reference/openapi.json) file under the `webhooks` section:

```yaml theme={null}
webhooks:
  job.contact.scored:
    post:
      summary: job.contact.scored webhook callback
      # Full specification in openapi.json
```

<Note>
  The webhook payload uses the same Contact schema as other API endpoints. This ensures consistency across the API and means any updates to the Contact schema automatically apply to webhook payloads.
</Note>
