Event subscription notification
product.listing_status_changed, product.variant_listing_status_changed, product.data_changed, and product.variant_pricing_changed are the supported event types.Headers
Every delivery includes:Host, Connection, Accept-Encoding). No other headers are sent — in particular no Accept, Accept-Language, Referer, Cookie, or tracing headers.
Deliveries are server-to-server requests from Headout’s infrastructure, not browser traffic: they carry no cookies, no Referer, and no browser identity. If a bot-protection or WAF layer sits in front of your endpoint, exempt the webhook path from browser heuristics (optionally scoping the exemption to the User-Agent prefix, which is easy to spoof and is not a security control), and verify every delivery’s X-Headout-Signature in your application as described below. A blocked request that is held open rather than rejected counts as a timeout on our side and is retried on the schedule below.
Signature verification vs. delivery authentication
These headers carry two independent security mechanisms — one is how Headout proves itself to you, the other is how Headout authenticates against your server:- Signature verification (
X-Headout-Signature) proves the payload came from Headout and was not modified in transit. It is always present, computed with the subscription’ssigningSecret. - Delivery authentication (
Authorization) is a value you configure on the subscription and Headout sends back verbatim, so your server (or a gateway in front of it) can apply its own access control.
Authorization header is configured — the Authorization value only controls access to your endpoint; it does not prove the payload is authentic or unmodified.
Verifying the signature
X-Headout-Signature has the form:
- Extract
tandv1from the header. - Compute
HMAC-SHA256(signingSecret, "<t>.<raw request body>")using the subscription’ssigningSecret(returned once from Create event subscription or Rotate signing secret), over the exact raw bytes of the request body — not a re-serialized version of it. - Hex-encode the result (lowercase) and compare it to
v1using a constant-time comparison.
t, so you can optionally add replay protection by rejecting deliveries whose t is too old.Response & retry behavior
Your endpoint should return a2xx status as soon as the event is durably received — do heavier processing asynchronously if needed.
2xx— Delivery successful, no retry.- Any other status, or a timeout — Treated as a failed delivery and retried. Non-2xx responses are not treated differently by status code today (a
4xxretries the same as a5xx). - Retries run on a backoff schedule of 5m → 30m → 3h → 18h → 24h, up to 6 attempts total. After the 6th failed attempt, the delivery is not retried again.
- Each delivery attempt allows roughly 15 seconds end-to-end for your endpoint to respond.
Ordering and deduplication
Deliveries are not ordered. Events are dispatched independently of each other, and a failed delivery is retried on the backoff above — so an event retried hours later can arrive after a newer event for the same product. Reconcile on the payload rather than on arrival order:id is opaque — don’t derive order from it.
Delivery is at-least-once, so the same id can arrive more than once: an attempt whose response was lost or timed out is retried even though you processed it. Make your handler idempotent on id.
createdAt, not a transition. Two changes in quick succession can collapse into a single event describing the net result, so apply the payload as the current state instead of replaying a sequence of flips.Reading a product.listing_status_changed event
listingStatus is the product’s status as of createdAt: LISTED means it is available to sell, UNLISTED means it is not — stop offering it until a later LISTED event arrives. A product can flip either way any number of times, so apply each event as the current state rather than as a one-off transition.
If you subscribe to both event types, a product being listed or delisted also produces a product.variant_listing_status_changed event for the same change. They are independent deliveries — neither their order nor their arrival together is guaranteed, so apply each on its own.
Reading a product.variant_listing_status_changed event
data carries only the variants that changed — listedVariantIds for newly listed, delistedVariantIds for newly unlisted. Variants that didn’t change are omitted, so apply both arrays against the variants you already hold rather than treating them as the full list.
For example, if product 3492 has variants 5397, 16552, 20770 and this event lists 52632, 83740 and unlists 5397, 16552, then 20770 is unchanged — leaving 20770, 52632, 83740 live.
Reading a product.data_changed event
Sent when the content of a listed product changes — the product was listed at both the previous and current sync and at least one tracked field now differs. A product being listed or unlisted is reported by product.listing_status_changed, and variants being added or removed by product.variant_listing_status_changed — not here.
data.changedFields lists the top-level fields that changed; it is never empty. Nested changes roll up to their top-level field, and any variant-level change collapses into a single variants entry. The event names what changed, not the new value — re-fetch the product with Get a product to read the updated data.
Product-level fields
variants entry (variant changes never appear individually):
product.variant_listing_status_changed for that.Reading a product.variant_pricing_changed event
Sent when the price of a variant changes for one of the next few dates — currently today, tomorrow, or the day after. It is a notification, not a price feed — the payload tells you which variant and date moved, never the new amounts. When one arrives, re-fetch the live price from the List inventory endpoint and update your side.
data carries three fields:
Body
Delivered webhook body. Event metadata wraps the event-specific data, whose shape is determined by type.
Unique identifier for this event, matching the X-Headout-Event-Id header. Stable across retries of the same delivery — use it to deduplicate.
"evt_019fdcf4-e289-72f6-b324-488d531377ab"
The event type. Determines the shape of data.
product.listing_status_changed, product.variant_listing_status_changed, product.data_changed, product.variant_pricing_changed When the event was created (ISO 8601, UTC). Stable across retries of the same event.
"2026-08-07T16:01:04.136037Z"
true for real production events; false for test/sandbox traffic.
The event-specific payload. Its shape is determined by type.
- product.listing_status_changed
- product.variant_listing_status_changed
- product.data_changed
- product.variant_pricing_changed
Response
Delivery successful. No retry.