# Change events

kwery can watch products you've already queried and push a webhook the moment something
meaningful changes — a price move, a buy-box flip, an offer-count change, or a previously
not-found product coming back. You don't re-poll and you don't diff snapshots yourself:
the diffing happens on our side, and you receive the change, signed, at your endpoint.

> **Rolling out.** Change-event subscriptions are being enabled account by account —
webhook delivery is not yet switched on for self-serve accounts.
[Contact us](https://kwery.co/contact) to join the rollout; we'll configure your watch
list, event types, and thresholds with you. Everything below documents the delivery
contract your receiver should implement — it is final and will not change.


## How watching works 

Any product you query through kwery is eligible for watching — the watch list is built from
the same `source` / `country` / `key` / `value` identities you already submit. A
**subscription** ties a watch list to a delivery endpoint and says which events you want:

- your `callback_url` (the same kind of endpoint as [stream webhooks](/guides/webhooks)),
- the event types you care about (see the catalog below — or just the umbrella event),
- optional noise controls: [thresholds](#thresholds) and [`delta_only`](#delta-only).


Subscriptions are configured per account — [contact us](https://kwery.co/contact) to set up
or tune yours (self-serve management from the dashboard is on our roadmap). The underlying
lookups that feed change detection are ordinary crawls, billed as normal credits; **event
deliveries themselves are never billed**.

## Event catalog 

| Event | Fires on |
|  --- | --- |
| `product.changed` | Umbrella event — fires on any significant change (price, offer count, or buy-box). One subscription that covers everything. |
| `price.delta` | The average/representative price for the product changed. |
| `buybox.change` | The Amazon buy-box winner or buy-box price changed. |
| `stock.update` | Availability or the number of active offers changed. |
| `product.resolved` | A product that was previously not-found is now found. |


Subscribe to the umbrella event alone, or to individual event types if you only care about,
say, price moves. Notes per event:

- **`price.delta`** — `changes` carries `price` as an old → new pair. "Representative
price" is the product's aggregate (the same basis as `price_avg` in results), so one
seller repricing inside a stable field doesn't fire it unless it moves the aggregate past
your threshold.
- **`buybox.change`** — Amazon only. `changes` carries `buybox_seller` and/or
`buybox_price`.
- **`stock.update`** — `changes` carries `offer_count` and/or `availability`.
- **`product.resolved`** — carries no `changes` block; the product's (re)appearance *is*
the signal. Useful for delisting monitoring: you learn the moment a competitor's product
is listed again.


## Payload reference 

Every change event is a `POST` to your `callback_url` with this body:

```json
{
  "event": "price.delta",
  "delivered_at": "2026-07-01T09:15:32.000Z",
  "product": {
    "source": "idealo",
    "country": "de",
    "key": "gtin",
    "value": "4006381333962",
    "name": "Fissler Original-Profi Collection Pan Set, 3-Piece"
  },
  "changes": {
    "price": { "old": 249.90, "new": 219.00 }
  }
}
```

| Field | Meaning |
|  --- | --- |
| `event` | One of the five event types above. |
| `delivered_at` | When this delivery was signed and sent (ISO 8601, UTC). |
| `product` | The identity you originally queried: `source`, `country`, `key`, `value`, plus the product `name` for human-readable logs. |
| `changes` | The changed fields, each an **old → new pair**. Keys depend on the event (`price`, `offer_count`, `availability`, `buybox_seller`, `buybox_price`). Absent for `product.resolved`. |


By default the event also includes the **full current record** alongside `changes`, so you
don't need a second lookup to get the rest of the product's fields.

## Delta-only mode 

If you only need the diff, set `delta_only: true` on the subscription: kwery then sends
just the `product` identity and the `changes` block — smaller payloads, useful for
high-volume watch lists where you keep your own product store anyway.

## Thresholds 

To avoid firing on noise — a cent of price jitter, a seller re-listing the same offer —
each subscription can carry change thresholds (minimum absolute or percentage move before
`price.delta` fires) and a margin floor. Thresholds are configured per subscription when we
set it up; tell us what "meaningful" means for your catalog and we tune to it.

## Delivery, signing, and retries 

Change events use the same delivery machinery as [Stream API webhooks](/guides/webhooks) — same
signing, same retry behavior, same replay. In full:

**Headers on every delivery:**

| Header | Content |
|  --- | --- |
| `X-Kwery-Signature` | `v1=` + HMAC-SHA256 of `"{timestamp}.{raw_body}"`, keyed with your `webhook_secret`. |
| `X-Kwery-Timestamp` | Unix seconds when the delivery was signed. |
| `X-Kwery-Delivery-Id` | Unique ID for this delivery — use it for idempotency and when talking to support. |


**Your endpoint should:** verify the signature against the **raw body** (constant-time
compare), reject timestamps older than 5 minutes (replay protection), respond `2xx`
quickly, and process asynchronously. The full verification walkthrough with Node.js and
Python code is in [Webhooks → Verify the signature](/guides/webhooks#3-verify-the-signature) —
the steps are identical for change events.

**Retries:** an immediate first attempt, then **+30 s → +2 min → +10 min → +30 min**
(5 attempts total, ±20% jitter). `3xx`, `5xx`, `429`, and network errors are retried; any
other `4xx` is treated as permanent. Exhausted deliveries are **dead-lettered and available
for replay** — same replay endpoints as stream deliveries.

**Idempotency:** deliveries can arrive more than once (a retry can race your slow `2xx`).
De-duplicate on `X-Kwery-Delivery-Id`.

## Questions we get 

- **What does this cost?** The crawls that feed change detection are ordinary lookups at
standard [credit rates](/guides/pricing#source-weights); deliveries are free. There is no
separate "events" fee.
- **How fast after a change do I get the event?** Detection happens when the product is
re-crawled — the event fires within that crawl's processing, so latency is bounded by
your watch list's crawl cadence, not by the delivery pipeline.
- **Can one change produce two events?** Yes, by design — a price move on a watched Amazon
product can fire both `price.delta` and the umbrella `product.changed` if you subscribed
to both. Subscribe to one level, or de-duplicate on (`product`, `delivered_at`).
- **What if my endpoint is down for an hour?** Retries cover ~43 minutes; anything past
that is dead-lettered and replayable — nothing is silently lost.


## Next steps

- [Webhooks](/guides/webhooks) — signature verification code, retry schedule, and replay
endpoints.
- [Trust](/guides/trust) — score the results your events point you at.
- [Destinations](/guides/destinations) — where change events (and batch/stream results) can be
delivered.