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

# Webhooks

> Receive real-time notifications when title entry state changes occur

## Overview

Cardinal Gray dispatches webhook notifications to your endpoint whenever a title entry undergoes a state change that matches your subscribed events. Webhooks are sent as `POST` requests with the full title entry as the JSON body.

<Note>
  To configure a webhook endpoint, contact the Cardinal Gray team. You'll need to provide your endpoint URL and choose an authentication scheme.
</Note>

## Authentication

Cardinal Gray supports two outbound authentication schemes for webhook delivery:

### Basic Auth

```
Authorization: Basic base64(username:password)
```

The `Authorization` header contains a Base64-encoded `username:password` pair. This is the recommended scheme for new integrations.

### Bearer Token (Legacy)

```
Authorization: Bearer YOUR_TOKEN
x-partner-key: YOUR_PARTNER_KEY
```

Legacy integrations may receive a bearer token in the `Authorization` header and/or an `x-partner-key` header. New integrations should use Basic Auth.

***

## Subscribable Events

| Event                       | Fires When                                                                                                              |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `title.job_status.complete` | Entry processing finishes successfully (`job_status` transitions to `complete`)                                         |
| `title.job_status.error`    | Entry processing fails terminally (`job_status` transitions to `error`)                                                 |
| `title.data.found`          | New enrichment data is discovered (NMVTIS, public portal, or private state pull transitions from `not_found` → `found`) |
| `title.data.not_found`      | Reserved for future use                                                                                                 |
| `title.account_status.*`    | Any `account_status` field change (wildcard — fires for all transitions)                                                |

### Account Status Lifecycle

The `title.account_status.*` event fires with the specific new status appended. Possible values:

```
title.account_status.originated
title.account_status.poa_sent
title.account_status.poa_signed
title.account_status.filing_en_route
title.account_status.filing_received
title.account_status.lien_delay
title.account_status.lien_error
title.account_status.lien_filed
title.account_status.lien_perfected
title.account_status.account_paid
title.account_status.lien_release_pending
title.account_status.lien_release_complete
```

### Physical Title Milestones

<Note>
  Physical-title milestone automation is enabled per organization during rollout.
</Note>

For titles whose original paper document is shipped to Cardinal Gray (e.g. Texas), a coarse `physical_title_status` field on the title entry tracks the return, with matching webhook events:

| Event                                     | Fires When                                                                                                                                                                                                       |
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title.physical_title_status.in_transit`  | The carrier has physically accepted and scanned the package containing the original title (label creation alone does not fire this)                                                                              |
| `title.physical_title_status.received`    | Cardinal Gray's receiving facility has logged and matched the physical title (carrier "Delivered" alone does not fire this)                                                                                      |
| `title.physical_title_status.unavailable` | The original title is lost or unavailable — Cardinal Gray will pursue a duplicate-title path instead of waiting for it. Recoverable: the status may later advance to in-transit or received if the title appears |
| `title.physical_title_status.skipped`     | No physical original is required for this transaction (e.g. an electronic title, or a filing scenario that does not require collecting the original)                                                             |

These milestones are independent of `account_status`; the title lifecycle statuses above are unaffected.

***

## Payload Shape

The webhook payload is a `POST` request with `Content-Type: application/json`. The body is the **full title entry** (identical to `GET /title/{id}` response) plus a `webhook_events` array indicating which subscribed events triggered this dispatch.

```json Example Webhook Payload theme={null}
{
  "entryId": "ffe3f543-cd92-4cb7-a78f-68a58b1fe8d3",
  "website": "yourorg.com",
  "VIN": "19UUA56602A000960",
  "state": "FL",
  "Borrower": "Jane Doe",
  "job_status": "complete",
  "account_status": "poa_signed",
  "account_data": {
    "vehicle": {
      "vin": "19UUA56602A000960",
      "year": "2002",
      "make": "ACURA",
      "model": "TL",
      "body_style": "SEDAN",
      "title": {
        "title_number": "123456789",
        "issuing_state": "FL",
        "issue_date": "2023-01-15",
        "odometer_reading": "98500",
        "odometer_status": "ACTUAL"
      },
      "owners": [
        {
          "name": { "first": "Jane", "middle": "M", "last": "Doe" },
          "address": {
            "street": "123 Main St",
            "city": "Miami",
            "state": "FL",
            "zip": "33101"
          }
        }
      ],
      "liens": [
        {
          "position": 1,
          "lienholder_name": "First National Bank",
          "date_filed": "2023-01-15"
        }
      ]
    },
    "loan": {
      "loan_amount": "25000",
      "borrower": [
        {
          "name": { "first": "Jane", "middle": "M", "last": "Doe" },
          "address": {
            "street": "123 Main St",
            "city": "Miami",
            "state": "FL",
            "zip": "33101"
          }
        }
      ],
      "lender": {
        "name": "Your Lending Co",
        "address": {
          "street": "456 Finance Ave",
          "city": "New York",
          "state": "NY",
          "zip": "10001"
        }
      }
    },
    "txn_quick_reference": "refi"
  },
  "nmvtis_found_status": "found",
  "nmvtis_pull": "https://s3-presigned-url...",
  "state_found_status": "found",
  "state_pull": "https://s3-presigned-url...",
  "signature_requests": [
    {
      "id": "NTRZ-abc123",
      "type": "RON",
      "status": "COMPLETED",
      "created_at": "2026-06-20T10:00:00Z",
      "updated_at": "2026-06-23T22:00:00Z",
      "signers": [
        { "name": { "first": "Jane", "last": "Doe" } }
      ],
      "documents_to_sign": [
        { "filename": "poa.pdf", "id": "doc-1", "signer_id": ["signer-1"] }
      ]
    }
  ],
  "fee_estimate": {
    "tax": { "taxable_amount": 25000, "total": 1750, "items": [] },
    "registration": { "total": 225, "items": [] },
    "title": { "total": 75, "items": [] },
    "submission_instructions": {
      "payable_to": "FL DMV",
      "address": { "street": "...", "city": "Tallahassee", "state": "FL", "zip": "32301" }
    }
  },
  "webhook_events": ["title.account_status.poa_signed"]
}
```

### Key Fields

| Field                 | Type       | Description                                                                                         |
| --------------------- | ---------- | --------------------------------------------------------------------------------------------------- |
| `entryId`             | `string`   | Unique identifier for the title entry                                                               |
| `website`             | `string`   | Organization identifier                                                                             |
| `VIN`                 | `string`   | Vehicle Identification Number                                                                       |
| `state`               | `string`   | Target state for title processing                                                                   |
| `job_status`          | `enum`     | Processing status: `pending`, `input_parsing`, `enriching`, `generating_forms`, `complete`, `error` |
| `account_status`      | `enum`     | Lifecycle status (see Account Status Lifecycle above)                                               |
| `account_data`        | `object`   | Structured vehicle, loan/sale, and owner data (see [Core Ontology](/guides/core-ontology))          |
| `nmvtis_found_status` | `enum`     | NMVTIS enrichment result: `pending`, `found`, `not_found`, `error`, `unavailable`, `skipped`        |
| `state_found_status`  | `enum`     | State/private enrichment result (same enum as above)                                                |
| `signature_requests`  | `array`    | Signing ceremony status (DocuSign, Notarize, etc.)                                                  |
| `fee_estimate`        | `object`   | Calculated DMV fees (tax, registration, title)                                                      |
| `webhook_events`      | `string[]` | Which subscribed events triggered this webhook delivery                                             |

<Note>
  S3 document paths (`nmvtis_pull`, `state_pull`, `form_*` fields) are delivered as **presigned URLs** valid for 1 hour. Download or process them promptly.
</Note>

***

## Delivery Semantics

* **Method**: `POST`
* **Content-Type**: `application/json`
* **Retry**: Exponential backoff on 5xx responses (up to 3 retries)
* **Timeout**: 10 second response timeout per attempt
* **Idempotency**: The same event may be delivered more than once. Use `entryId` + `webhook_events` to deduplicate.

### Success / Failure

| Your Response | Our Behavior                                      |
| ------------- | ------------------------------------------------- |
| `2xx`         | Delivery confirmed, no retry                      |
| `4xx`         | Client error logged, no retry (fix your endpoint) |
| `5xx`         | Retried with exponential backoff                  |
| Timeout       | Treated as 5xx, retried                           |

***

## Testing

To trigger a test webhook:

1. Create a title entry via `POST /title` with enrichment enabled (`sync_nmvtis: true`)
2. Wait for `job_status` to reach `complete` — this fires `title.job_status.complete` and potentially `title.data.found`
3. Update `account_status` via the dashboard or signing flow to trigger `title.account_status.*` events

You can verify your endpoint received the payload and responded with `2xx`. Contact the Cardinal Gray team if you need help debugging delivery issues.
