Skip to main content
Order events carry a full Order snapshot in data.order so your handler can act without a follow-up GET. The order invoice events carry the full Invoice plus the same Order snapshot. Eagle Eye events carry the watch context plus the vehicles or vehicle IDs that changed. Chat events carry a preview of a dealer-visible chat message so you can alert the dealer without polling the widget stream.
Order payload parity (v1.18+): the data.order field in every order webhook is structurally identical to the response of GET /v1/orders/{id} — same field names, types, nullability, and enrichment (including is_highest_bid, current_max_bid_usd, and — since v1.60 — the invoice summary). The same holds for data.order on order.invoice_issued. You can reuse the same Order type and deserializer across order reads and order webhooks.Eagle Eye vehicle parity: vehicle objects inside eagle_eye.match additions and price_changes use the same VehicleSummary schema returned by GET /v1/vehicles. Eagle Eye adds watch and signal metadata around that vehicle object; it does not define a separate vehicle model.Examples below are abbreviated for brevity. Order webhook payloads include every field documented under Get an order, not only the ones shown here. Eagle Eye vehicle examples are also abbreviated; build against the shared VehicleSummary schema.

order.detail_updated

Fires when an existing order is materially updated: prices, VIN, plate, options, inspection information, shipment, or other order details. Changes to a competing bid that affects the order snapshot also notifies it. Invoice edits use order.invoice_updated instead. This is an Order event; it does not replace Eagle Eye watch notifications. data.order is the full current partner-facing Order, including its invoice summary and vehicle snapshot. Admin-only fields are never included. Admin/API/background writes and data repairs are captured. No-op and timestamp-only writes are silent; offline orders remain silent. Creation alone is not a detail update. Rapid edits can combine into one latest-state notification on the next delivery worker run. This is not a history of every intermediate edit. It has its own event ID and uses the normal signing, retry, and replay rules. Existing events such as order.status_changed and order.invoice_issued cover their own lifecycle operations: status transitions and invoice issue/reissue do not also queue this event. Automatic draft creation during a status transition is included in the status event. Separate detail edits remain eligible; handle snapshots idempotently. occurred_at is the last captured change time, which may differ from order.updated_at when a related invoice changes. Replay always resends the saved snapshot rather than rebuilding it. Abbreviated example (the actual order contains the complete Order resource):

order.status_changed

Fires on every server-side status transition. Includes the previous status for reconstruction.

Status transitions that fire this event

secondary_inspection_ready is a decision point with two possible drivers: admin or the partner directly (confirm/decline). Both drivers fire this webhook — the partner’s event pipe carries every outcome of the decision point (amended 2026-07-06; see below). Partner-initiated transitions (placed creation, placed → cancelled via DELETE, in_transit → customs, customs → delivered) are confirmed by the API response and do not fire order.status_changed. Status transitions and creation alone do not queue order.detail_updated. The exception is the secondary_inspection_ready decision point: secondary_inspection_ready → acquiring via POST /v1/orders/{id}/confirm and secondary_inspection_ready → cancelled via DELETE /v1/orders/{id} (cancellation_reason: secondary_inspection_declined) do fire order.status_changed (previous_status: secondary_inspection_ready), in addition to the synchronous API response.

Inspection-ready example (v1.49)

When the inspection completes, the ready-transition event carries the LMN URL of the inspection report (data.order.secondary_inspection_url, nullable) so you can surface it to your dealer at the confirm/decline decision point:
The URL stays on the order afterwards (GET /v1/orders/{id} and later webhooks include it too). It is null when no report link was recorded at the transition.

Failed-auction example

failure_reason enum (terminal failed, reachable from acquiring only):
  • outbid — lost to external bidder at auction.
  • outbid_internally — another partner’s higher bid won. All competing placed orders on the same auction lot enter acquiring together before resolution, so each competitor sees a clean placed → acquiring → failed sequence.
  • vehicle_unavailable — LMN’s dealer purchase attempt failed (car sold to another buyer).
  • auction_cancelled — auction event cancelled after LMN began bidding.
  • auction_passed — vehicle passed at auction, OR zombie cleanup at auction_date + 24h (see below).
  • seller_withdrew — seller withdrew the car after LMN started attempting to acquire.

Zombie-cleanup variant

If LMN never picked up a placed order and the auction has been over for 24 hours, the system auto-resolves it with failure_reason: auction_passed. The webhook collapses the synthetic intermediate state — previous_status is placed (not acquiring):
Treat this as a regular failed event. You can recognize the zombie-cleanup variant by the combination previous_status: "placed" + failure_reason: "auction_passed" + acquired_at: null — a normally-failed auction order transitions from acquiring, not placed.

Cancellation example

cancellation_reason enum (terminal cancelled, reachable from placed or secondary_inspection_ready):
  • dealer_cancelled — partner sent DELETE while placed.
  • auction_rescheduled_too_soon — LMN auto, auction rescheduled past order cutoff.
  • auction_cancelled — LMN auto, auction event cancelled before LMN started bidding.
  • seller_withdrew — LMN auto, seller pulled dealer listing before LMN started purchase.
  • secondary_inspection_failed — LMN/admin cancelled at secondary_inspection_ready (inspection found disqualifying issues); this one fires order.status_changed.
  • secondary_inspection_declined — partner sent DELETE while secondary_inspection_ready. Partner-initiated, but this decision does fire order.status_changed (previous_status: secondary_inspection_ready, amended 2026-07-06) in addition to being echoed on GET /v1/orders/{id} and in the DELETE response.
failure_reason and cancellation_reason are mutually exclusive — an order has one or the other, never both. For reasons that can appear in either enum (auction_cancelled, seller_withdrew), the order’s current status at the moment of the upstream event decides the terminal.

order.auction_rescheduled

Fires when the auction source postpones or advances an auction, shifting auction_date and recomputing order_cutoff_at. No status change.
Compare data.previous_auction_date against data.order.auction_date to detect direction and magnitude. Update dealer-facing deadlines accordingly. If a reschedule moves auction_date so close that order_cutoff_at is already in the past on a placed order, LMN auto-cancels with cancellation_reason: "auction_rescheduled_too_soon" and fires a separate order.status_changed event. Orders already in acquiring or later are unaffected (LMN is committed; the auction proceeds on the new date).

order.price_updated

Fires when the vehicle backing an active placed order has its listing_price updated by the upstream scraper. No status change. The payload keys (previous_listing_usd / current_listing_usd) keep the _usd suffix for webhook-event convention; they refer to the same underlying value as pricing.listing_price on the vehicle. Useful for partners whose bid strategy depends on the current market estimate — a price drop may mean the dealer should raise max_bid_amount_usd to stay competitive, while a spike may mean the current max_bid is now insufficient.
Semantics:
  • delta_usd is negative when the price dropped (partner-friendly).
  • delta_pct is rounded to 1 decimal place and is null if previous_listing_usd was 0.
  • Fires at most once per actual change — identical refreshes to the same value are deduped server-side.
  • Fires only while the order is in placed — terminal-state and post-acquiring orders ignore market refreshes.

order.re_auctioned

Fires when a vehicle tied to one of your terminal orders (status failed / cancelled / delivered) reappears in a new auction round. Matched by Korean license plate across auction houses, so the same physical car is caught even if it moves from Glovis to SK or Lotte between listings.
Use case: the dealer originally bid, lost (or cancelled), and wants a second chance. On receiving this event, surface the new vehicle_id back to the dealer UI and let them re-POST /v1/orders if still interested. Semantics:
  • The attached data.order is the original terminal order — NOT a new order. No new order is implicitly created.
  • Fires at most once per (license_plate, new_auction_date) — no duplicates during re-scrapes.
  • data.previous_vehicle_id is the ID from the terminal order; data.new_vehicle_id is the current listing’s ID.
  • data.new_auction_date is in KST ISO 8601 with +09:00 offset.

order.invoice_updated

Fires when an order’s invoice changes outside issue/reissue: manual draft creation, draft edits (including consignee, terms, and amounts), payment recording, or voiding. The payload contains the full partner-safe data.order and current data.invoice. No-op, timestamp-only, and admin-only changes are silent. Rapid edits may combine into one latest-state notification. This does not also emit order.detail_updated. Automatic draft creation during a status transition is covered by order.status_changed. Issue and reissue emit only order.invoice_issued for the publication operation. For Shekel’s 1:1 order/invoice flow, key these events by data.order.id. The invoice resource retains its invoice ID and version. Existing saved invoice.issued events retain their original payload and type when replayed; new publications use the new name.

order.invoice_issued

Fires when LMN issues the commercial invoice for an order, and again on every re-issue. data.invoice is the full Invoice exactly as published for that version — its document_url is pinned to ?version=N and keeps returning that version’s PDF after later re-issues. data.order is the full Order snapshot at publication, including the nested invoice summary (parity with GET /v1/orders/{id}). Always on; no opt-in. See Invoices for the lifecycle.

Re-issue

Same event type, same number, version + 1; data.previous_version is the version being replaced. Here LMN completed the packing list after loading:
Semantics:
  • previous_version is null on version 1 and version − 1 afterwards. issued_at is the first publication and does not move on re-issue.
  • Dedupe on the envelope id. To keep one record per invoice, key on data.invoice.number and replace your copy when data.invoice.version is higher than the one you hold; ignore equal or lower versions (deliveries are at-least-once and may arrive out of order).
  • Not fired for draft creation, paid, or void — those emit order.invoice_updated (except automatic draft creation during a status transition), and remain readable via Order.invoice and the Invoices routes.
  • Delivery: identical signing, retry, and replay semantics as every other event — see signing and retries. The event row is written in the same transaction as the invoice version, so an issued version is never missing its event.

eagle_eye.match

Fires when a saved Eagle Eye watch has at least one addition, price change, or removal. The payload is batched per watch and includes enough context for a partner UI to explain why the vehicle appeared. Test events: a sandbox test fire (POST /v1/eagle-eye/watches/{watch_id}/test-webhook, or the console’s “Send test webhook” action) delivers this same event type with data.is_test: true added. The key is present on every test event and never on organic ones — check it before downstream automation acts on an addition. See Eagle Eye → Testing your receiver.

Relisted auction vehicles

A confirmed relist reuses this event and appears as a fresh entry in data.additions[]; there is no eagle_eye.relisted event. When LMN can link the new auction occurrence to a prior one by exact normalized VIN, the addition includes these optional fields together:
  • availability_reason: "relisted"
  • previous_vehicle_id: the latest strictly earlier listing occurrence on this watch
Both fields are omitted when VIN is missing, invalid, or ambiguous. Their absence is not a claim that the car has never appeared before. Dedup remains occurrence-based (watch_id + the new vehicle id), so each later auction can produce one fresh addition.

Signal detail

Signal-driven matches include signal_detail so product teams can show the dealer why the alert matters: price_drop is drop-only. A price increase does not carry signals.price_drop; if the vehicle still qualifies, the current match updates silently and no price_changes webhook entry is emitted. If the increase makes it fail the watch’s filters, LMN sends a removal.

Static lists

The rule above — drop-only, no emission on NULL→value, no emission on rises — applies only to automatic watches (automatic_list_update: true). On a static list (automatic_list_update: false), explicit membership changes still happen through POST …/matches / POST …/matches/remove (no webhook fires for those calls — you performed the action yourself). Once a car is a member, every price transition LMN observes on it emits via this webhook: a price appearing on a previously un-priced listing (NULL→value — the core static-list use case), a drop, and a rise. These entries carry match_reason: ["manual"] instead of filters / signals.*, and never carry signal_detail — signals cannot be configured on static lists. Auction static membership also follows the same physical car into a later auction when an exact, unambiguous VIN link exists. LMN replaces the completed listing id with the new occurrence as one logical membership and emits a relisted additions entry. A hidden member follows silently and remains hidden. Missing or ambiguous VIN leaves the member pinned to the old occurrence; LMN never guesses from make/model or plate alone. A non-hidden static-list member that disappears from live inventory (sold/delisted) still produces a removals entry with reason: "auction_complete", same as an automatic watch. See Eagle Eye endpoints — Static lists for how cars enter and leave a static list.

Removal reasons

under_contract is the only reversible reason, and it’s static-list-only. Contracts fall through. When that happens, a static-list member reappears in the match list as a new entry in additions on this same eagle_eye.match event — there is no separate event type for a revival. On an automatic (filter-based) watch, the same real-world event still removes the dealer car from the match list, but is reported as reason: "delisted" — automatic watches don’t distinguish a contract from a genuine sale; the car can still come back as a plain new additions entry once it’s purchasable again, just without the distinct reason label on the way out. Either way, don’t treat a dealer-car removal as necessarily final. This list is not exhaustive. Handle an unrecognized reason defensively (per conventions) rather than with a switch that has no default case — LMN may add reasons over time. Hiding a match via POST /v1/eagle-eye/watches/{watch_id}/matches/hide is a view-state change on your side only — it never produces a removals entry on this webhook. Use GET /v1/eagle-eye/watches/{watch_id}/events to recover the exact webhook payload history for a watch.

chat.message.created

Fires when LMN produces a dealer-visible message in your embedded chat — an AI agent reply or a human staff reply. One event per message. A question that triggers an inventory search may produce two events in quick succession: a brief interim acknowledgement (e.g. “Let me check inventory…”) followed shortly by the full reply. Receivers should expect this and not treat it as a duplicate.
Off by default. This event is high-volume, so it isn’t sent unless your integration has subscribed. Contact LMN to enable it.
Field notes:
  • external_user_id is the id your widget JWT supplied for this dealer — use it to route the alert to the right user. It can be null (the dealer-identity lookup is defensive and the event still fires if it misses) — null-check before routing and fall back to session_id.
  • display_name can also be null for the same reason as external_user_id above.
  • session_id is the chat thread (one per dealer) — the openable unit for a “you have a new message” link. Use this as your routing fallback when external_user_id is null.
  • message_id is ULID-ordered — use it for ordering and unread cursors, not occurred_at.
  • text_preview is capped at 240 characters and never includes tool payloads or UI block data. The full conversation is available to the dealer in the widget; has_ui_blocks tells you a rich card (e.g. a vehicle match list) accompanied the text but isn’t itself serialized here.
  • kind is assistant for AI agent replies or staff for a human operator reply (takeover mode). Both are treated the same way for alerting purposes.
  • origin can be null for messages that predate the widget-origin field; treat a null origin as unknown surface, not an error.
  • direction is always outbound today — reserved for future inbound/system message types.
Delivery: identical signing, retry, and idempotency semantics as every other event — see signing and retries. Deliveries are at-least-once, so dedupe on the envelope id. Ordering across retries is not guaranteed; order by data.message_id, not by delivery arrival.

chat.eagle_eye.match_posted

Fires after LMN posts an Eagle Eye match digest into a dealer’s chat room — the same room-visible message the dealer already sees in the widget. Only fires for chat-created (room-associated) watches: watches built through a chat proposal (create_watch / update_watch), which carry a chat_session_id. API-created watches (created directly via POST /v1/eagle-eye/watches) have no room and remain webhook-only — this event never fires for them; they continue to receive eagle_eye.match directly, same as today.
Off by default. Like chat.message.created, this event isn’t sent unless your integration has subscribed. Contact LMN to enable it.
Field notes:
  • session_id is the dealer’s chat room — the deep-link/routing target, same role it plays in chat.message.created.
  • external_user_id can be null — dealer identity is resolved defensively by (chat_session_id, partner_id, environment); a missing or ambiguous match never guesses a dealer, it emits null instead. Route by session_id when null.
  • display_name can also be null for the same reason.
  • watch_id / watch_name identify the Eagle Eye watch that changed; watch_name is null for unnamed watches.
  • chat_message_id is the id of the digest message already posted into the room (visible to the dealer in the widget) — use it to deep-link straight to that message, or to dedupe against your own read of the room.
  • match_event_id correlates this ping to the eagle_eye.match webhook delivery that triggered it. No vehicle payload is duplicated here — the full additions / price_changes / removals entries live on that event; join on this id to fetch them.
  • counts is a summary only — additions, price_changes, removals mirror the lengths of the corresponding arrays on the paired eagle_eye.match event.
Delivery: identical signing, retry, and idempotency semantics as every other event — see signing and retries. Deliveries are at-least-once; dedupe on the envelope id.