# Get price-drop and availability webhooks

> Register HTTPS webhooks for price drops, availability changes and completed refreshes, verify HMAC signatures, and deduplicate retries by event id.

*Source: https://shopsavvy.com/data/documentation/webhooks* · Part of the [ShopSavvy Data API documentation](https://shopsavvy.com/data/documentation.md)

---

Register webhook URLs to get notified when prices drop, product availability changes, or scheduled refreshes complete.

## Create Webhook

**Endpoint:** `POST /v1/webhooks`

**Request Body:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `url` | string | Yes | HTTPS URL to receive webhook POST requests |
| `events` | string[] | Yes | Event types: `price_drop`, `availability_change`, `schedule_completion` |

**Example:**
```bash
curl -X POST 'https://api.shopsavvy.com/v1/webhooks' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"url": "https://your-app.com/webhooks/shopsavvy", "events": ["price_drop", "availability_change"]}'
```

The response includes a `secret` field (shown once) — use it to verify webhook signatures.

## Webhook Delivery Format

Every delivery is a JSON POST shaped like this:

```json
{
  "id": "8f14e45f-ceea-467a-9d2b-1c8f0b1a9c33",
  "event": "price_drop",
  "attempt": 1,
  "timestamp": "2026-08-10T18:04:11.529Z",
  "data": { "product": { }, "retailer": "amazon", "price": { }, "availability": { } }
}
```

`id` is the stable identifier for the event. `attempt` is the 1-based delivery attempt — `1` on the first delivery, `2` through `5` on retries. Both are also sent as headers, `X-ShopSavvy-Event-Id` and `X-ShopSavvy-Delivery-Attempt`, so you can check them without parsing the body.

## Deduplicating Retries

Delivery is at-least-once. If your server processes a request but the response is lost, times out, or returns a non-2xx status, we treat the delivery as failed and retry it — so you can receive the same event more than once.

`id` stays the same across the first attempt and every retry of that event, and is different for every new event. Record the ids you have processed and ignore a repeat. `timestamp` and the signature change on every attempt and must not be used for this.

`attempt` tells you which delivery of that event you are looking at without keeping any state of your own — `attempt > 1` means we did not see a 2xx for an earlier delivery of the same `id`. It is informational; dedupe on `id`.

## Verifying Webhook Signatures

Every webhook delivery includes an `X-ShopSavvy-Signature` header with format: `t=<timestamp>,v1=<signature>`.

To verify: compute `HMAC-SHA256(secret, "<timestamp>.<request_body>")` and compare to `v1`.

## List Webhooks

`GET /v1/webhooks` — Returns all your registered webhooks with status and failure counts.

## Update Webhook

**Endpoint:** `PUT /v1/webhooks/:id`

Update an existing webhook without losing its signing secret. All fields are optional, but at least one must be provided. Omitted fields are left unchanged — so you can flip `is_active` without resending `url` or `events`.

| Field | Type | Description |
|-------|------|-------------|
| `url` | string | New HTTPS URL to receive webhook POST requests |
| `events` | string[] | Replacement event list: `price_drop`, `availability_change`, `schedule_completion` |
| `is_active` | boolean | Enable (`true`) or disable (`false`) the webhook |

**Example — disable a webhook without deleting it:**
```bash
curl -X PUT 'https://api.shopsavvy.com/v1/webhooks/wh_abc123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"is_active": false}'
```

Re-enabling a webhook (`is_active: true`) resets its consecutive-failure counter, so a webhook that was auto-disabled after repeated failures starts fresh.

## Test Webhook

`POST /v1/webhooks/:id/test` — Sends a test event to verify your URL is reachable.

## Delete Webhook

`DELETE /v1/webhooks/:id` — Permanently removes a webhook and all its event history.

**Retry Policy:** Failed deliveries are retried with exponential backoff (1min, 5min, 30min, 2hr, 12hr). After 5 failed attempts per event, the event is marked as permanently failed. After 50 consecutive failures, the webhook is automatically disabled.

**Limits:** Maximum 5 webhooks per account. Maximum 3 event types per webhook.
