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

# Webhooks

> Receive real-time notifications when data changes in AutosnapAI

Webhooks let AutosnapAI push events to your server instead of making you poll. When a vehicle is added, updated, removed, or when an inventory import completes, we send a signed HTTP POST to a URL you configure.

## When to use webhooks

* **Real-time inventory sync** — keep your database in lockstep with AutosnapAI without polling
* **Marketing automation** — trigger an email when a "Great Deal" vehicle hits inventory
* **Data warehouse pipelines** — stream events into BigQuery, Snowflake, or similar
* **Alerting** — notify your team when an important dealer's inventory stops updating

## Event types

### Website Inventory Events

| Event             | Fires when                                                                  |
| ----------------- | --------------------------------------------------------------------------- |
| `vehicle.created` | A new vehicle appears in scraped inventory                                  |
| `vehicle.updated` | A vehicle's fields change (price drop, description update, photos added)    |
| `vehicle.removed` | A vehicle is removed from active inventory (sold, transferred, or delisted) |
| `import.complete` | A scheduled or on-demand inventory import finishes for a dealership         |
| `setup.complete`  | A dealer setup job finishes successfully                                    |
| `setup.failed`    | A dealer setup job fails                                                    |

### Dealer Health Check Events

| Event                        | Fires when                                                                             |
| ---------------------------- | -------------------------------------------------------------------------------------- |
| `dealer.config.auto_updated` | A dealership's scraper configuration was automatically repaired (e.g. changed site ID) |
| `dealer.provider.changed`    | A dealership switched to a different website provider                                  |

### IMS Feed Events

| Event                 | Fires when                                                                                 |
| --------------------- | ------------------------------------------------------------------------------------------ |
| `ims.import.complete` | An IMS feed file was successfully imported (new/updated/removed vehicle counts in payload) |
| `ims.feed.stale`      | An IMS feed file has not been updated within the configured staleness threshold            |
| `ims.vehicle.created` | A new vehicle appears in an IMS feed import                                                |
| `ims.vehicle.updated` | An existing IMS vehicle's data changes                                                     |
| `ims.vehicle.removed` | A vehicle is no longer present in the IMS feed (marked inactive)                           |

<Note>
  Website inventory events and IMS feed events are independent. If a dealership has both a website scraper and an IMS feed, you'll receive events from both sources. Use the event prefix (`ims.*` vs no prefix) to distinguish the source.
</Note>

## Creating a webhook

You provide a `secret` (minimum 16 characters) when creating the webhook. This secret is used to sign every delivery so you can verify it came from AutosnapAI.

```bash theme={null}
curl -X POST "https://api.autosnap.com/v1/webhooks" \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "'$AUTOSNAP_API_KEY'",
    "url": "https://yourapp.com/webhooks/autosnap",
    "event_types": ["vehicle.created", "vehicle.updated", "vehicle.removed", "import.complete", "ims.vehicle.created", "ims.vehicle.updated", "ims.vehicle.removed", "ims.import.complete"],
    "secret": "your_secret_at_least_16_chars",
    "dealership_id": "dlr_8cfc0b00a98b"
  }'
```

```json Response theme={null}
{
  "success": true,
  "created": [
    { "webhook_id": 42, "event_type": "vehicle.created" },
    { "webhook_id": 43, "event_type": "vehicle.updated" },
    { "webhook_id": 44, "event_type": "vehicle.removed" },
    { "webhook_id": 45, "event_type": "import.complete" },
    { "webhook_id": 46, "event_type": "ims.vehicle.created" },
    { "webhook_id": 47, "event_type": "ims.vehicle.updated" },
    { "webhook_id": 48, "event_type": "ims.vehicle.removed" },
    { "webhook_id": 49, "event_type": "ims.import.complete" }
  ],
  "skipped": [],
  "url": "https://yourapp.com/webhooks/autosnap",
  "dealership_id": "dlr_8cfc0b00a98b"
}
```

<Note>
  The API creates one webhook record per event type. If you subscribe to 4 event types, the response `created` array will have 4 entries, each with its own `webhook_id`.
</Note>

## Webhook delivery format

Every webhook delivery is an HTTP POST with these headers:

```
POST /webhooks/autosnap HTTP/1.1
Host: yourapp.com
Content-Type: application/json
X-Autosnap-Signature: a3f2c8b4e9d1f7a2b5c8d3e6f9a1b4c7d0e3f6a9...
X-Autosnap-Event: vehicle_created
User-Agent: AutoSnap-Webhook/1.0
```

The payload structure:

```json theme={null}
{
  "event": "vehicle.created",
  "dealership_id": "dlr_8cfc0b00a98b",
  "timestamp": "2026-04-13T14:32:19.123456+00:00",
  "data": {
    "vin": "5GAEVBKSXTJ122446",
    "make": "Buick",
    "model": "Enclave",
    "year": 2026,
    "price": 49430
    /* ... full vehicle object ... */
  }
}
```

| Field           | Description                                              |
| --------------- | -------------------------------------------------------- |
| `event`         | The event type (e.g. `vehicle.created`)                  |
| `dealership_id` | Public dealership ID (`dlr_xxxx`)                        |
| `timestamp`     | ISO 8601 timestamp of when the event was fired           |
| `data`          | The event-specific payload (see per-event schemas below) |

### Per-Event Data Schemas

#### `setup.complete`

```json theme={null}
{
  "website_url": "https://baydodge.net",
  "provider": "DealerInspire",
  "configuration_id": 42,
  "vehicle_counts": { "new": 85, "used": 120 },
  "status": "active"
}
```

#### `setup.failed`

```json theme={null}
{
  "website_url": "https://baydodge.net",
  "error": "Unable to detect inventory provider"
}
```

#### `import.complete`

```json theme={null}
{
  "website_url": "https://baydodge.net",
  "stats": {
    "total_received": 205,
    "new": 3,
    "updated": 198,
    "removed": 4,
    "unchanged": 0
  }
}
```

#### `vehicle.created` / `vehicle.updated` / `vehicle.removed`

The `data` field contains the vehicle object directly (not wrapped in a `{"vehicle": {...}}` envelope):

```json theme={null}
{
  "vin": "5GAEVBKSXTJ122446",
  "year": 2026,
  "make": "Buick",
  "model": "Enclave",
  "condition": "New",
  "price": 49430
}
```

<Note>
  **Events are scoped per dealership.** Each event's top-level envelope includes the `dealership_id` the event belongs to. When a dealer group shares used-inventory across multiple rooftops, the same VIN may generate parallel events from each participating dealership — each event is legitimate for its own dealership scope. Deduplicate on `(dealership_id, vin)` if you need a unified view. See [Concepts: Vehicles — Core identifiers](/concepts/vehicles#core-identifiers) for details.
</Note>

#### `ims.import.complete`

Both events fire for every IMS feed import. If you subscribe to both, you will receive two webhooks per import with the same payload. Subscribe to one or the other, not both.

```json theme={null}
{
  "feed_config_id": 1,
  "file_name": "MP14015.csv",
  "vehicle_count": 381,
  "new_count": 3,
  "updated_count": 378,
  "removed_count": 2
}
```

#### `ims.feed.stale`

```json theme={null}
{
  "feed_config_id": 1,
  "dealership_id": "dlr_8cfc0b00a98b",
  "provider": "vauto",
  "staleness_hours": 53.2,
  "threshold_hours": 48
}
```

#### `ims.vehicle.created` / `ims.vehicle.updated` / `ims.vehicle.removed`

```json theme={null}
{
  "vin": "1GCHSCEA7L1214921",
  "stock_number": "M1382",
  "year": 2020,
  "make": "Chevrolet",
  "model": "Colorado 2WD",
  "condition": "Used",
  "price": 18999
}
```

<Warning>
  For vehicle events (`vehicle.created`, `vehicle.updated`, `vehicle.removed`), the `data` field contains the vehicle object **directly** — it is NOT wrapped in a `{"vehicle": {...}}` envelope.
</Warning>

## Signature verification

**Always verify the signature on every webhook** before acting on the payload. Anyone can POST to a public URL — signatures prove the request actually came from AutosnapAI.

The `X-Autosnap-Signature` header contains an HMAC-SHA256 hex digest of the raw request body, computed using the `secret` you provided when creating the webhook:

```
HMAC-SHA256(secret, raw_request_body)
```

<CodeGroup>
  ```python Python theme={null}
  import hmac, hashlib

  def verify_webhook(secret: str, signature_header: str, raw_body: bytes) -> bool:
      expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
      return hmac.compare_digest(expected, signature_header)
  ```

  ```javascript Node theme={null}
  const crypto = require('crypto');

  function verifyWebhook(secret, signatureHeader, rawBody) {
    const expected = crypto
      .createHmac('sha256', secret)
      .update(rawBody)
      .digest('hex');

    return crypto.timingSafeEqual(
      Buffer.from(expected),
      Buffer.from(signatureHeader)
    );
  }
  ```
</CodeGroup>

## Retry and delivery semantics

* **At-least-once delivery.** You may receive the same event more than once — **always make your handler idempotent**.
* **Retry schedule.** Failed deliveries are retried up to 3 total attempts with exponential backoff (approximately 4 seconds, then 8 seconds between retries). Only server errors (5xx) trigger retries — client errors (4xx) are not retried.
* **Auto-disable.** After reaching the `max_failures` threshold (default 10, configurable when creating the webhook) of consecutive failures, the webhook subscription is automatically deactivated. Use the [reactivate endpoint](/api-reference/endpoints/reactivate-webhook) to re-enable it.
* **Timeout.** Your endpoint must respond within **10 seconds**. Longer responses are treated as failures. Do the minimum work synchronously (queue the event for background processing) and return a 2xx immediately.
* **Ordering.** Events within a single dealership are generally ordered, but cross-dealership ordering is not guaranteed.

## Inspecting deliveries

```bash theme={null}
curl "https://api.autosnap.com/v1/webhooks/{webhook_id}/deliveries?api_key=$AUTOSNAP_API_KEY&limit=50"
```

Returns delivery attempts (default 50, max 100) with status codes, response times, and error messages. Use the `limit` query parameter to control how many results are returned. Useful for debugging when events aren't reaching your server.

## Managing webhooks

```bash theme={null}
# List all webhooks
curl "https://api.autosnap.com/v1/webhooks?api_key=$AUTOSNAP_API_KEY"

# Reactivate a disabled webhook (resets failure count)
curl -X PATCH "https://api.autosnap.com/v1/webhooks/{webhook_id}/reactivate?api_key=$AUTOSNAP_API_KEY"

# Reactivate ALL disabled webhooks
curl -X PATCH "https://api.autosnap.com/v1/webhooks/reactivate-all?api_key=$AUTOSNAP_API_KEY"

# Delete a webhook
curl -X DELETE "https://api.autosnap.com/v1/webhooks/{webhook_id}?api_key=$AUTOSNAP_API_KEY"
```

## Related

<CardGroup cols={2}>
  <Card title="Handling webhooks" icon="webhook" href="/guides/handling-webhooks">
    Full end-to-end guide with receiver code and deployment tips
  </Card>

  <Card title="Webhook endpoints" icon="code" href="/api-reference/endpoints/create-webhook">
    Full webhook API reference
  </Card>
</CardGroup>
