Events reference
Every event FurniOps publishes, generated from the API's own contract. What each one means, which channels deliver it (webhooks, the change feed, the stream), whether test mode can emit a sample, and its payload.
Every change FurniOps makes is an event. Catalogue and source events reach you three ways (webhooks, the change feed and the stream); events about your organisation itself reach your webhook endpoints only. Every event shares one envelope:
{
"id": "evt_034XqBaL2mRLTZPXyRbaUi",
"type": "offer.changed",
"spec_version": "1.0",
"source": "furniops/catalog",
"subject": "ofr_034XqBaL2mRLTZPXyRbaUg",
"time": "2026-10-04T10:00:02Z",
"tenant_id": "org_034XqBaL2mRLTZPXyRbaUj",
"version": 12,
"schema_version": 1,
"correlation_id": "run_034XqBaL2mRLTZPXyRbaUk",
"causation_id": null,
"livemode": true,
"data": {
"id": "ofr_034XqBaL2mRLTZPXyRbaUg",
"variant": "var_034XqBaL2mRLTZPXyRbaUf",
"source": "src_034XqBaL2mRLTZPXyRbaUh",
"catalogue": "cat_034XqBaL2mRLTZPXyRbaUd",
"location": "DAL",
"availability": "in_stock",
"version": 12,
"facets": ["price"],
"fields": [
{
"path": "cost",
"old": 89900,
"new": 94900,
"provenance": {
"source": "src_034XqBaL2mRLTZPXyRbaUh",
"method": "mapped",
"confidence": 1,
"observed_at": "2026-10-04T10:00:00Z"
}
}
]
}
}
Each field is described in The event envelope. Deduplicate
by id, which is also the webhook-id header of every delivery, and apply by version.
Every event
This table is generated from the contracts release these docs are built from. Each type links to its payload, field by field.
| Type | Sent when | Delivered by | Sample |
|---|---|---|---|
| catalogue.unverified | A vendor catalogue is no longer verified by its brand: FurniOps took an approved claim back. | Webhooks, feed, stream | Yes |
| catalogue.verified | A vendor catalogue is now verified by its brand: FurniOps approved the brand's claim. | Webhooks, feed, stream | Yes |
| credits.exhausted | Available credit reached zero: once today's allowance is spent too, Ask AI answers 429 ai_budget_reached. | Webhooks only | No |
| credits.expiring | A lot of credit expires in 30 days with credit left in it, once per lot. | Webhooks only | No |
| credits.low | Available credit fell below the organisation's low_balance_micros, once each time it crosses. | Webhooks only | No |
| credits.purchased | Stripe reported a credit purchase's invoice paid and its credit was added: once per purchase. | Webhooks only | No |
| delivery.disabled | FurniOps disabled a webhook endpoint: no delivery succeeded for 72 hours, or it answered 410 Gone (status_reason). Sent to the organisation's other endpoints that subscribe to it. | Webhooks only | No |
| export.completed | An export succeeded or failed. Read it (GET /v1/exports/{id}) for its files, or its error. | Webhooks only | No |
| offer.changed | A source's offer changed (price, stock or availability; see facets): the changed fields. A source that stops stating an offer withdraws it: withdrawn_at is set, quantity becomes 0, incoming empty and availability out_of_stock (facets stock and availability); stating it again clears withdrawn_at. | Webhooks, feed, stream | Yes |
| offer.created | A source offered a variant at a location for the first time: every field. | Webhooks, feed, stream | Yes |
| product.created | A master product was made from a source record: every field, with old: null. | Webhooks, feed, stream | Yes |
| product.discontinued | A master product was discontinued: its sources stopped listing it for long enough (the absence ladder). | Webhooks, feed, stream | Yes |
| product.merged | A master product was merged into another (matching or a person): it now redirects to merged_into, and reads of its ID answer with the survivor. | Webhooks, feed, stream | Yes |
| product.published | A vendor's product became visible to the network (its catalogue was published, or the product listed again): every field, with old: null, like product.created. Its variants and offers follow as their own .created events. | Webhooks, feed, stream | Yes |
| product.unmerged | A merge was undone: the product is its own again under its original ID, with every field. | Webhooks, feed, stream | Yes |
| product.updated | A master product's data or lifecycle status changed (other than to discontinued): the changed fields. | Webhooks, feed, stream | Yes |
| product.withdrawn | A vendor's product left the network (its catalogue was withdrawn, or the product hidden): no fields. Drop it, its variants and its offers; a later product.published brings it back. | Webhooks, feed, stream | Yes |
| proposal.applied | A proposal was applied: accepted by a person, or at once as low risk under auto_low_risk. | Webhooks only | No |
| proposal.created | An AI agent proposed a change that passed its proof and waits for a person: accept or reject it (POST /v1/proposals/{id}/accept). | Webhooks only | No |
| source.blocked | A source kept failing (or its URL is refused) and is now blocked: no runs until the organisation sets it active. status_reason says why. | Webhooks, feed, stream | Yes |
| source.degraded | A source failed several runs in a row and is now degraded; it is still scheduled. status_reason says why. | Webhooks, feed, stream | Yes |
| source.recovered | A degraded source ran successfully and is active again. | Webhooks, feed, stream | Yes |
| usage.threshold_reached | A meter reached 80% or 100% of its monthly quota in live mode, once a month per meter and threshold. At 100% the metered action is refused (429 quota_exceeded) until the month ends. | Webhooks only | No |
| variant.created | A master variant was made from a source record: every field, with old: null. | Webhooks, feed, stream | Yes |
| variant.merged | A master variant was merged into another (the same item from two sources): it now redirects to merged_into, and its offers moved there. | Webhooks, feed, stream | Yes |
| variant.unmerged | A variant merge was undone: the variant is its own again under its original ID, with every field and its offers back. | Webhooks, feed, stream | Yes |
| variant.updated | A master variant's data changed: the changed fields. | Webhooks, feed, stream | Yes |
| webhook.test | A test event, sent only when asked for (POST /v1/webhook-endpoints/{id}/test), whatever the endpoint subscribes to. | On request | No |
- Webhooks, feed, stream: delivered by webhooks, and kept in the change feed and the stream, subject to licences.
- Webhooks only: about your organisation, not a catalogue's records. Only your endpoints of the
event's mode that subscribe to the type (or to
*) receive it. - Sample:
POST /v1/test/eventscan emit a sample of it in test mode.
Subscribing
An endpoint's event_types names the types it receives, or * for every public type, present
and future. New types are added without a new API version, so a receiver should ignore a type it
does not know. webhook.test is sent only when you ask for one, whatever an endpoint subscribes
to.
In the dashboard
Developers › Events lists the events of the mode you are in, filtered by type, each with its payload and the endpoints it went to.
Webhook endpoints and deliveries
The full reference for webhooks. Endpoints and their modes, event types and filters, thin and fat payloads, burst windows, versions, limits, the delivery log, the retry schedule, automatic disabling, recovery, resending, secrets and the dashboard's controls.
Handle webhooks securely
Verify Standard Webhooks signatures with or without the SDK, refuse replays, rotate secrets without downtime, and answer in a way FurniOps understands.