cypress.DOCS
Log inRegister
Cypress Docs
Developers

Webhooks

Signed event envelopes at your endpoint — vocabulary, verification, retries, and the delivery log.

TL;DRAI summary

Webhooks notify you about events in Cypress, like a vehicle being purchased. You'll receive a secure, detailed message for each event, with built-in retries and logs to help you keep track of everything.

Dive deeper:

Register a callback and Cypress POSTs one envelope per event — your own headers echoed back, an HMAC signature to verify, retries with backoff, and a self-service delivery log when anything misbehaves.

The envelope

{
  "href": "https://app.getcypress.ai/api/v1/listings/clx…",
  "eventType": "LISTINGS.PURCHASED",
  "resource": "listings",
  "body": { "vin": "1FTFW1ET5DFC10312", "status": "SOLD", "salePrice": 21900 },
  "createdOn": "2026-08-19T16:04:12.00Z",
  "relatedResources": [{ "href": "…/api/v1/vehicles/clx…" }]
}

Headers on every delivery:

HeaderMeaning
your registered headersEchoed verbatim — your endpoint's own auth
x-cypress-eventThe event type, in your dialect
x-cypress-deliveryUnique delivery id — dedupe on this; retries re-send it
x-cypress-signatureHMAC-SHA256 hex of the raw body, keyed by your signing secret

body reflects the resource's state at delivery time; href always serves the latest. (BUY.* events are the exception: their body is the moment the event happened — see Buy planning events.) Deliveries may arrive out of order — order by createdOn.

Verifying the signature

import { createHmac, timingSafeEqual } from "crypto";

function verify(rawBody, signatureHeader, signingSecret) {
  const expected = createHmac("sha256", signingSecret)
    .update(rawBody, "utf8").digest("hex");
  return signatureHeader?.length === expected.length &&
    timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected));
}

The signing secret is issued once, when the subscription is created.

Two vocabularies

Each subscription has a dialect: CYPRESS (VEHICLES.*, LISTINGS.*) or MANHEIM (UNITS.*, OFFERINGS.*) — same events, your naming. Subscriptions created through /api/compatible/v1/subscribers default to MANHEIM. Filters (eventTypes) accept either vocabulary.

The dialect follows the subscription, not the surface you push through: a vehicle created via modern POST /api/v1/vehicles still arrives as UNITS.CREATED in Manheim shape (with hrefs pointing at /api/compatible/v1/…) if the subscription's dialect is MANHEIM. Check the endpoint's badge on your Integrations page — it says "Manheim names" — and recreate the subscription with "dialect": "CYPRESS" if you want modern names and shapes. Either way, vehicle event bodies carry the pricing map (buyNowPrice, floorPrice, startBidPrice), condition and — since 1.1.0 — lifeStage.

The event catalog

FamilyEventsNotes
VehiclesVEHICLES.CREATED VEHICLES.UPDATED VEHICLES.DISPOSITION.STAGED VEHICLES.DISPOSITION.RESOLVED VEHICLES.DELETED VEHICLES.IMPORT.COMPLETED VEHICLES.IMPORT.STAGED VEHICLES.IMPORT.APPLIEDInventory pushes, every change from every door, the sold-elsewhere review loop, a vehicle deleted before it sold, and a CSV import finished (the last two opt-in, 1.5.0)
ListingsLISTINGS.CREATED LISTINGS.UPDATED LISTINGS.ACTIVATED LISTINGS.PAUSED LISTINGS.RESUMED LISTINGS.CANCELLED LISTINGS.NOSALE LISTINGS.PURCHASEDThe selling lifecycle; PURCHASED carries the sale price
OffersOFFERS.CREATED OFFERS.ACCEPTED OFFERS.DECLINED OFFERS.COUNTERED OFFERS.EXPIRED OFFERS.WITHDRAWNThe negotiation — autoProcessed: true when a pricing rule decided. ACCEPTED is sent for every accepted offer, see below. WITHDRAWN (since 1.5.0): the buyer withdrew an open offer on your vehicle
Offers you madeMARKETPLACE.OFFER.UPDATEDThe buyer's side: an offer your organization made was accepted, declined, countered or expired (API 1.5.0) — opt-in, Cypress dialect only, marketplace:read; see below
Cars shared with youMARKETPLACE.SHARE.RECEIVEDA seller addressed a share link to your organization (API 1.9.0): the link with its token, the car's public facts and who shared it — opt-in, Cypress dialect only, marketplace:read; see Sales channels
ClosingCLOSING.STARTED CLOSING.STEP.COMPLETED CLOSING.DOCUMENT.GENERATED CLOSING.DOCUMENT.SIGNED CLOSING.PAYMENT.RECEIVED CLOSING.CANCELLED CLOSING.COMPLETEDThe whole closing, step by step — see Due diligence & closing. COMPLETED also fires for a sale recorded off-platform, with offPlatform: true in its context
Closing, both sidesCLOSING.UPDATEDEvery move of a closing, told to the seller and the buyer, each with its side (API 1.5.0) — opt-in, Cypress dialect only, closing:read; see below
PlanningPLAN.VEHICLE.ENTERED PLAN.VEHICLE.SCHEDULED PLAN.WINDOW.DUE PLAN.WINDOW.MISSED PLAN.BLOCKER.ADDED PLAN.BLOCKER.RESOLVED PLAN.HANDED_TO_SELL PLAN.GOAL.SET PLAN.QUARTER.APPROVED PLAN.VEHICLE.UPDATED PLAN.STRATEGY.UPDATEDThe plan's decisions (API 1.3.0); a goal set, a quarter approved, a vehicle unscheduled, parked or moved, and the strategy set or ended since 1.5.0 — opt-in, Cypress dialect only, see below and the 1.5.0 events
Buy planningBUY.TARGET.MATCHED BUY.TARGET.FILLED BUY.TARGET.ENDED BUY.TARGET.CREATED BUY.TARGET.DUE BUY.QUARTER.APPROVED BUY.TARGET.UPDATED BUY.QUARTER.SETThe dealer's buy plan (API 1.4.0); a target added, changed or removed, a target's needed-by day come, a quarter's goal set and a quarter approved since 1.5.0 — opt-in, Cypress dialect only, see below
TasksTASK.CREATED TASK.ASSIGNED TASK.DUE TASK.COMPLETED TASK.CANCELLEDYour organization's tasks, people's and agents' alike (API 1.5.0) — opt-in, Cypress dialect only, see below
NotificationsNOTIFICATION.CREATEDA notification sent to one of your members, with its recipient (API 1.5.0) — opt-in, Cypress dialect only, notifications:read; see Notifications
Closing deadlines and requestsCLOSING.STEP.DUE CLOSING.STEP.OVERDUE CLOSING.DOCUMENT.REQUESTEDAn open closing step's deadline is under 12 hours away, or past, or a document was asked for, told to both sides (API 1.5.0) — opt-in, Cypress dialect only, closing:read; see Closings
MarketingCAMPAIGN.SENT CAMPAIGN.PAUSED CAMPAIGN.ACTIVATED CAMPAIGN.INTEREST CONTACT.UNSUBSCRIBED PROMOTION.UPDATEDA campaign switched on, sent or paused, a person it mailed opened one of your cars, a contact of your book unsubscribed, a promotion published, paused, edited or deleted (API 1.5.0) — opt-in, Cypress dialect only, marketing:read; see Marketing
AutomationsAUTOMATION.PUBLISHED AUTOMATION.SENT_BACK AUTOMATION.RUN.COMPLETED AUTOMATION.RUN.FAILEDCypress published or sent back an automation you submitted; a run of one of your own automations finished, or stopped on an error (API 1.5.0) — opt-in, Cypress dialect only, automations:read, only while your Automations product is on; see Automations
CRMCONTACT.CREATED CONTACT.UPDATED CONTACT.DELETED BUSINESS.CREATED BUSINESS.UPDATED BUSINESS.DELETED DEAL.CREATED DEAL.UPDATED DEAL.STAGE_CHANGED DEAL.DELETED OUTREACH.LOGGED OUTREACH.REPLIED CRM.IMPORT.COMPLETEDYour own CRM book, for a system that keeps it in step both ways, deletes and imports included (API 1.5.0) — opt-in, Cypress dialect only, crm:read; see below
InsightsINSIGHT.CREATED INSIGHT.ANSWEREDSequoia put a recommendation in front of your organization, or a person answered one (API 1.5.0) — opt-in, Cypress dialect only, insights:read; see below
Your accountORGANIZATION.UPDATED MEMBER.UPDATED BANKING.UPDATED BILLING.UPDATEDYour organization's profile, settings and documents; who can act for it; its bank accounts and lenders; money on its account (API 1.5.0) — opt-in, Cypress dialect only, each on the read scope of its area; see below
TestPING.TESTYour test-sends

Documents arrive as data

CLOSING.DOCUMENT.GENERATED and DOCUMENT.SIGNED include the full data-sheet JSON the template rendered from — the bill of sale's figures as fields, not just a PDF link — plus provenance (sheet revision, signature plan). Payment events carry method, reference and amount.

Vehicle changes, from every door (1.5.0)

VEHICLES.UPDATED is sent for every change to a vehicle, not only the API's: a person in the app, a CSV import, an automation, or one of the app's actions called through the API or Cypress Operate. Besides the vehicle, its body carries changedFields (what changed: status, labelSlug, photos, shareLinks, valuation…), toStatus when the status moved, and source (app, csv, automation or api), so your system can tell its own writes from everyone else's. A valuation sends it only when someone asked for it (Revalue); the scheduled refreshes do not.

Since 1.8.0, an inspection report read from its link sends it with changedFields: ["inspectionReport"] and inspectionReport, as the vehicle's read has it. Its status is IMPORTING_PHOTOS when the grade and damages are in, COMPLETE when the photos are too, FAILED when the report or its photos could not be read. This holds for a report attached from any door: the API, the app or an import. Manheim-dialect subscriptions get the event without the inspectionReport body. See Inspection reports.

Archive requests and their decision

VEHICLES.DISPOSITION.STAGED is sent when a request to archive one of your vehicles, or to record it sold elsewhere, waits for Cypress's review, from any door: the API (DELETE /api/v1/listings/{id}, POST /api/v1/vehicles/{id}/disposition, a full-inventory push), the app, or an import. Since 1.8.0 that is only a vehicle with an open offer or a sale in progress. VEHICLES.DISPOSITION.RESOLVED is sent with the decision; an archive applied at once (no open offer, no sale in progress, or planning stock) sends RESOLVED alone, with automatic: true. Besides the vehicle, their bodies carry:

FieldSTAGEDRESOLVED
requestIdThe requestThe same request
dispositionARCHIVED or SOLD_ELSEWHEREOn an approval
decision—APPROVED or REJECTED (with notes)
automatic—true when the archive applied at once, with no review (since 1.8.0)
reviewReasonOPEN_OFFERS or SALE_IN_PROGRESS: why Cypress reviews it (since 1.8.0)—
statusBeforeThe vehicle's status when the request was filedThe same
statusAfterAfter the filing: ON_HOLD when its live listing was paused for the review, else unchangedAfter the decision: ARCHIVED when approved; when rejected, LISTED again if the request had paused it, else as it was
pausedWhether its listing was paused—
sourceapi, api-sync (a full-inventory push), csv or appThe same
syncIdThe full-inventory push's label, when it sent oneThe same
liveDealA full-inventory push's flag: the vehicle's open offers or its sale in progress—

statusBefore, statusAfter, source, syncId, automatic and reviewReason are new in 1.8.0; nothing was removed or renamed, and the body's own status stays the vehicle's status when the event is delivered. Manheim-dialect subscriptions get these events exactly as before 1.8.0, without the new fields.

VEHICLES.DELETED tells a bridge to drop its copy of a vehicle deleted before it sold (a draft, one in review or approved), in the app or over the API. Its body is the vehicle's vin, the status it had and source. It is opt-in: only a Cypress-dialect subscription that lists it hears it, with the vehicles scope.

Offer events (1.4.0)

OFFERS.ACCEPTED is sent once for every accepted offer, however it was accepted:

  • the seller, or Cypress on the seller's behalf, accepts it;
  • the vehicle's pricing rules accept it as it arrives (autoProcessed: true);
  • a buyer uses Buy Now (autoProcessed: true, immediately after OFFERS.CREATED);
  • the buyer accepts the seller's counter;
  • a price change a Cypress manager approved accepts an offer that now qualifies.

Before 1.4.0 the last two accepted the offer and sent nothing, so an endpoint that listens for OFFERS.ACCEPTED (or has no filter) now receives events it did not before. The body is unchanged: nothing was added to any event. An accepted offer is announced to an endpoint once, however many places report it (the delivery remembers the offer it came from).

Planning events (1.3.0)

PLAN.* events are Sell planning's: they follow your organization's plan — see the Planning API for when each fires and the envelope body (resource plan, href the vehicle's plan view, openBlockers a count). The dealer's side, Buy planning, has its own family, BUY.*, on a switch of its own. Three rules keep PLAN.* away from integrations that did not ask for it:

  • Opt-in. A PLAN.* event goes only to a subscription that lists it in eventTypes. A subscription without a filter ("all events") never receives one.
  • Cypress dialect only. A MANHEIM-dialect subscription never receives them, and a subscription that would be MANHEIM and lists one is refused with 422 (on /api/v1/webhooks/subscriptions, /api/compatible/v1/subscribers and the Integrations page alike).
  • Planning. Subscribing to one needs the planning:read scope, and no new PLAN.* event is sent once your organization's Planning is switched off (a delivery already under way still completes its retries).

The same three rules cover vehicle events about planning stock: VEHICLES.* for a vehicle in Operation or Planning (a planning push, a PATCH, a departure staged or resolved) reaches only a Cypress-dialect subscription that lists the event, while the organization has Planning — an unfiltered or MANHEIM subscription never sees planning stock. When a cohort confirm hands vehicles to Sell, each sends a plain VEHICLES.UPDATED, delivered to every matching subscription like any Sell event.

Buy planning events (1.4.0)

BUY.* events are the dealer's side: they tell your purchasing system what happens to the targets of your buy plan. They follow the same three rules as PLAN.*, on a switch of their own:

  • Opt-in. A BUY.* event goes only to a subscription that lists it in eventTypes. A subscription without a filter ("all events") never receives one.
  • Cypress dialect only. A MANHEIM-dialect subscription never receives them, and a subscription that would be MANHEIM and lists one is refused with 422 (on /api/v1/webhooks/subscriptions, /api/compatible/v1/subscribers and the Integrations page alike).
  • Buy planning. Subscribing to one needs the buy-planning:read scope (buy-planning:write holds it too) and an organization with Buy planning: without the scope the answer is 403 SCOPE_MISSING, without the product 403 BUY_PLANNING_NOT_ENABLED. No new BUY.* event is sent once your organization's Buy planning is switched off (a delivery already under way still completes its retries). Sell planning being on does not open them, nor the other way round.
curl -X POST "$BASE/api/v1/webhooks/subscriptions" \
  -H "x-api-key: $KEY" -H "content-type: application/json" \
  -d '{
    "callback": "https://purchasing.example.com/cypress",
    "eventTypes": ["BUY.TARGET.MATCHED", "BUY.TARGET.FILLED", "BUY.TARGET.ENDED"]
  }'

The key needs both webhooks and buy-planning:read. Every event is for your own organization and goes whatever a target's track and notice setting say: the alert emails are for people, these are for systems.

EventFires whenbody
BUY.TARGET.MATCHEDA live listing fits an open target for the first timetarget (id, label, periodKey) and match, exactly as GET /targets/{id}/matches returns it
BUY.TARGET.FILLEDA target has nothing left to buytarget, exactly as GET /targets/{id} returns it, with its bought, inClosing and remaining counts
BUY.TARGET.ENDEDA target's quarter closed with units still shorttarget, shortfall (the units never bought) and bought

The envelope's resource is buy. href serves the target as it is now (…/targets/{id}, and for MATCHED its matches, …/targets/{id}/matches).

BUY.TARGET.MATCHED fires when the hourly match run first stores a listing against a target, once per target and listing: the run after it repeats the event under the same id, so a run that was cut off loses nothing, and your endpoint still receives each id once. A listing that is delisted and listed again, or one you hid, is not told again. It is never sent for a target of a quarter that is over, nor for a target that is already bought in full, and a demo organization never hears of a real listing (or the other way round). When you add a target, the next run tells you of every listing that already fits it, one event each, so a new target can start with a burst.

BUY.TARGET.FILLED fires on the transition: the target had units left to buy and now has none. That happens when a closing that fits it starts (a purchase in progress counts, as inClosing, so bought can be below quantity), when a purchase is recorded or moved to it, and when its quantity is lowered to what you have bought. A target can come open again (a closing falls through, a purchase is removed, the quantity is raised) and fill again: that is a second FILLED. Nothing is sent when it comes open.

BUY.TARGET.ENDED fires from the daily run for a target that reads Ended in the app: its quarter is over, units remain, and you did not close the target (one you closed reads Closed, and one bought in full reads Filled; neither is told). A quarter is over from the first instant after its last day, UTC; the run looks back two days, so a day it misses loses nothing.

{
  "href": "https://app.getcypress.ai/api/v1/buy-planning/targets/clx…/matches",
  "eventType": "BUY.TARGET.MATCHED",
  "resource": "buy",
  "body": {
    "target": { "id": "clx…", "label": "Toyota RAV4 XLE", "periodKey": "2026-Q4" },
    "match": {
      "vehicleId": "clx…", "url": "https://app.getcypress.ai/marketplace/clx…",
      "year": 2022, "make": "TOYOTA", "model": "RAV4", "trim": "XLE", "mileage": 31000, "state": "FL",
      "ask": 26400, "isNew": true, "score": 100, "kind": "EXACT", "misses": []
    }
  },
  "createdOn": "2026-10-11T14:00:03.120Z",
  "relatedResources": []
}
{
  "href": "https://app.getcypress.ai/api/v1/buy-planning/targets/clx…",
  "eventType": "BUY.TARGET.ENDED",
  "resource": "buy",
  "body": {
    "target": {
      "id": "clx…", "label": "Chevrolet Equinox", "status": "OPEN", "displayStatus": "ENDED", "source": "MANUAL",
      "track": "BALANCED", "priority": 0,
      "make": "Chevrolet", "model": "Equinox", "trims": [], "planningClass": null, "bodyClasses": [], "fuelTypes": [], "conditions": [],
      "yearMin": null, "yearMax": null, "mileageMin": null, "mileageMax": null, "states": [],
      "quantity": 4, "needFrom": null, "needBy": "2026-09-27", "needByWeek": "W39", "periodKey": "2026-Q3",
      "targetUnitPrice": 24000, "maxUnitPrice": 26000,
      "bought": 1, "inClosing": 0, "remaining": 3,
      "note": null, "createdAt": "2026-07-02T15:10:00.000Z", "updatedAt": "2026-07-02T15:10:00.000Z"
    },
    "shortfall": 3,
    "bought": 1
  },
  "createdOn": "2026-10-01T00:00:00.000Z",
  "relatedResources": []
}

BUY.TARGET.FILLED carries the same target object, with displayStatus FILLED and remaining 0; createdOn is when the target filled, and for BUY.TARGET.ENDED the moment its quarter closed.

A BUY.* body is the moment the event happened. It is built then, from the same target and match objects the Buy planning reads return, and every retry sends that same body. It is not the state at delivery: read href for the target as it is now.

Once per endpoint. Each BUY.* event is delivered to an endpoint once, however many times its cause is noticed: a match is told on the run that first stores it and never again for that target and listing, a target's fill is told on the purchase that fills it and not by a later pass that counts the same purchase, and a rerun of the daily run, or the second day it looks back over, sends no ENDED twice. The rule for retries is the usual one: dedupe on x-cypress-delivery. A Redeliver from the delivery log sends that delivery again, on purpose.

What a body never carries. The dealer's own target fields and a listing's public facts only: never a seller's name, id or organization, a floor, thresholds or limits, a VIN, the staff view that fits a seller's limits to your target, or the token that groups a seller's listings. See what the matches never carry.

Task events (1.5.0)

TASK.* events follow your organization's tasks: the work items people, automations and connected agents share, on contacts, companies, deals, vehicles, sell goals and windows, and buy targets. An agent listens for TASK.ASSIGNED to learn it has work, and for TASK.DUE to learn it is time.

  • Opt-in. A TASK.* event goes only to a subscription that lists it in eventTypes. A subscription without a filter ("all events") never receives one.
  • Cypress dialect only. A MANHEIM-dialect subscription never receives them, and a subscription that would be MANHEIM and lists one is refused with 422 (on /api/v1/webhooks/subscriptions, /api/compatible/v1/subscribers and the Integrations page alike).
  • tasks:read. Subscribing to one needs the tasks:read scope (tasks:write holds it too): without it the answer is 403 SCOPE_MISSING. There is no product switch: every organization has tasks.
curl -X POST "$BASE/api/v1/webhooks/subscriptions" \
  -H "x-api-key: $KEY" -H "content-type: application/json" \
  -d '{
    "callback": "https://agents.example.com/cypress",
    "eventTypes": ["TASK.ASSIGNED", "TASK.DUE"]
  }'
EventFires whenAdded to body
TASK.CREATEDA task is added to your organization's book, by a person, an automation or an agent
TASK.ASSIGNEDA task is handed to someone other than whoever acted: at creation or when its assignee changes. A person giving a task to themselves is not a hand-off; an automation's assignment always isassigneeUserId, who it was given to then
TASK.DUEA task's due time arrives while it is open; told once per task and due timedueDate, the due time that arrived
TASK.COMPLETEDA task is marked done
TASK.CANCELLEDA task is cancelled

Reopening, editing and deleting a task send nothing.

TASK.DUE is checked every 15 minutes, so it arrives within about a quarter of an hour of the due time. It looks back two days, so a missed run loses nothing, and a task overdue for longer than that is not told. Each due time is told once (the event's id carries it), whoever holds the task; moving the due date makes the new one due in its turn. When a person holds the task, Cypress also reminds them in the app; an agent user is only ever told through this event.

The envelope's resource is tasks and href is /api/v1/tasks/{id}. Unlike BUY.*, the body is the task as it is at delivery time, the same object for every event: title, status, due date, who made it and from where (source: PERSON, AUTOMATION or AGENT, with an agent's playKey), who holds it and who finished it (each a name and kind, PERSON or AGENT, never an email), and links, what it is about (since 1.9.0 also links.conversation, the conversation a task was made from). A task deleted before its delivery answers { "id": "…", "deleted": true }. When the task is about a vehicle, the vehicle is among relatedResources.

{
  "href": "https://app.getcypress.ai/api/v1/tasks/clx…",
  "eventType": "TASK.ASSIGNED",
  "resource": "tasks",
  "body": {
    "id": "clx…", "title": "Call the buyer back about the RAV4", "notes": null,
    "status": "OPEN", "dueDate": "2026-11-02T15:00:00.000Z", "channel": "PHONE",
    "source": "PERSON", "playKey": null,
    "assignee": { "id": "clx…", "name": "Scout", "kind": "AGENT" },
    "createdBy": { "id": "clx…", "name": "Ana Ruiz", "kind": "PERSON" },
    "completedBy": null, "completedAt": null,
    "links": { "contactId": "clx…", "dealId": null, "businessId": null, "vehicleId": "clx…",
               "planningGoalId": null, "saleWindowKey": null, "lotKey": null, "buyTargetId": null,
               "app": "https://app.getcypress.ai/go/task/clx…" },
    "createdOn": "2026-10-15T14:00:00.000Z", "updatedOn": "2026-10-15T14:00:00.000Z",
    "assigneeUserId": "clx…"
  },
  "createdOn": "2026-10-15T14:00:00.120Z",
  "relatedResources": [{ "href": "https://app.getcypress.ai/api/v1/vehicles/clx…" }],
  "links": { "app": "https://app.getcypress.ai/go/task/clx…" }
}

A subscription filtered by entityIds receives the events of the tasks about those records: a contact, company, deal, vehicle, sell goal or buy target id. Since 1.9.0 it may also list a conversation's room id, to receive the events of the tasks made from that conversation (see A task from a conversation).

Opening the record. A Cypress-dialect envelope carries links.app, where the event's record opens in the app (the vehicle for a listing's events, the target for a Buy planning event), on the app's host. See Opening a record in the app. The Manheim envelope stays exactly Manheim's shape.

CRM, plan and insight events (1.5.0)

These follow the same rules as the task events: opt-in (only a subscription that lists them by name), Cypress dialect only, and the read scope of their area to subscribe. Each body is the record as the API reads it, so a bridge can apply it without a second call.

EventWhenBody
CONTACT.CREATED BUSINESS.CREATED DEAL.CREATEDA contact, company or deal entered your book: typed in the app, pushed over the API, imported, or opened from an offerThe record as GET /api/v1/contacts/{id} (/businesses/{id}, /deals/{id}) returns it, your externalIds included, read when the event is delivered
CONTACT.UPDATED BUSINESS.UPDATED DEAL.UPDATEDFields of one changedThe record, plus changedFields
DEAL.STAGE_CHANGEDA deal moved to another stage, by a person or because its offers moved itThe deal, plus fromStage, toStage and automatic (true when its offers moved it). A stage move alone sends only this event
OUTREACH.LOGGEDAn interaction going out: an email sent through Cypress, a call or a meeting loggedThe interaction as the record's interactions read returns it, on the record it was logged on
OUTREACH.REPLIEDAn interaction coming in, logged by someone. Cypress does not receive replies to its emails (they reach the sender's own mailbox), so a reply is told when it is loggedThe interaction
PLAN.GOAL.SETA goal of the quarter was added, changed or removed (Sell planning)change (ADDED, CHANGED or REMOVED) and goal, as GET /api/v1/planning/goals lists it (a removed one: its id, kind and label), with its periodKey
PLAN.QUARTER.APPROVEDA person approved the quarter's plan (Sell planning)quarter: periodKey, approvedAt, approvedBy, approvedPlanVersion and how many goals, lot targets and scheduled vehicles the approved plan holds. Once per approved version
BUY.TARGET.CREATEDA target was added: on the Targets tab, over the API, or kept from Sequoia's drafttarget, as GET /api/v1/buy-planning/targets/{id} returns it
BUY.TARGET.DUEAn open target's needed-by day came with units still to buytarget and remaining. Once per target and needed-by day (the daily run looks back two days, so a missed day loses nothing)
BUY.QUARTER.APPROVEDA person approved the quarter's buy plan, on the quarter bar or with Sequoia's draft keptquarter: periodKey, approvedAt, approvedBy, unitsGoal, budget and how many targets it holds
INSIGHT.CREATEDSequoia put a recommendation in front of you: a new one, or one back after its answer ran out (a "remind me next week" that came due)insight, as GET /api/v1/insights shows it, side (SELL or BUY) and about (the vehicles or lot, or the buy target)

The events that tell what a person or an agent did to these (a record deleted, an import finished, a target paused, a recommendation answered) are in Every action heard.

  • Only your own book and plans. Cypress's own CRM book tells no one.
  • In the event log, endpoint or not. Since 1.9.0 the plan, buy-plan and insight events are recorded in your event log whether or not a subscription lists them (the plan events while Planning is on, the buy-plan events while Buy planning is on), so an app that reads GET /api/v1/events instead of taking webhooks hears them too. They are still delivered only to a subscription that lists them. Before 1.9.0 they were recorded only while a subscription listed them.
  • Scopes and switches. CRM events need crm:read; INSIGHT.CREATED needs insights:read. PLAN.* events need planning:read and Planning on, BUY.* events buy-planning:read and Buy planning on, as before.
  • links.app opens the record, the target, or what an insight is about. A goal and a quarter have no page of their own, so their events carry none.
curl -X POST "$BASE/api/v1/webhooks/subscriptions" \
  -H "x-api-key: $KEY" -H "content-type: application/json" \
  -d '{
    "callback": "https://crm-bridge.example.com/cypress",
    "eventTypes": ["CONTACT.CREATED", "CONTACT.UPDATED", "DEAL.STAGE_CHANGED", "OUTREACH.LOGGED"]
  }'

Every action heard (1.5.0)

Every action a person can take in the app can be taken through the API and Cypress Operate (the app's actions), and every one that changes something is told. The events below close the gaps: what the older events did not say. Each is opt-in (only a subscription that lists it by name), Cypress dialect only, and needs the read scope of its area. Each body says what happened in change, and where it came from in source: app (a person on a screen), api (a key, an agent or Cypress Operate), csv, automation or stripe. A body names what changed, never a secret: no account number, no signing link, no document content. href reads the thing as it is now.

EventScopechangeBody
MARKETPLACE.OFFER.UPDATEDmarketplace:readACCEPTED DECLINED COUNTERED EXPIREDSent to the buyer: offerId, vehicleId, your amount, the seller's counterAmount on a counter, and a reason when it is one the app shows a buyer (ANOTHER_OFFER_ACCEPTED, VEHICLE_OFF_MARKET, PRICE_CHANGED…). Never the seller's floor, rules or notes. Your own counter is not told back to you
CLOSING.UPDATEDclosing:readSTARTED STEP_COMPLETED DEAL_SETUP_UPDATED DEAL_SETUP_CONFIRMED DOCUMENT_UPLOADED DOCUMENT_REMOVED DOCUMENT_ACCEPTED DOCUMENT_REJECTED DOCUMENT_GENERATED DOCUMENT_SIGNED SENT_FOR_SIGNATURE PAYMENT_RECEIVED INCIDENT_REPORTED INCIDENT_UPDATED ROLLED_BACK CANCELLED COMPLETEDSent to both sides, each with its side (SELLER or BUYER), the stepKey and the document or incident it is about. The commission step is the seller's alone
ORGANIZATION.UPDATEDorganization:readPROFILE DELEGATION STATUS PRODUCTS PROFILES CHANNELS SETTINGS DOCUMENT_UPLOADED DOCUMENT_REMOVED DOCUMENT_REVIEWED SELLER_TERMS STOREFRONT ACCOUNT_RULES DEALER_REQUESTWhat changed (changedFields, the setting's kind and id, the document type). STATUS, PRODUCTS, CHANNELS, DOCUMENT_REVIEWED and ACCOUNT_RULES (your features and limits, 1.9.0) are Cypress's moves on your account; DEALER_REQUEST (1.9.0) is an individual's request to become a dealer, with its status (PENDING, APPROVED, DECLINED)
MEMBER.UPDATEDmembers:readADDED ROLE_CHANGED REMOVED RESTORED UPDATEDuserId, role, previousRole and email
BANKING.UPDATEDbanking:readADDED CHANGED ARCHIVEDresource (BANK_ACCOUNT or LENDER), its id and changedFields. Never a number: a payout account changed without anyone hearing is the classic fraud path, so every change is told
BILLING.UPDATEDbilling:readINVOICE_PAID PAYMENT_RECEIVED CREDIT_NOTE_ISSUED CREDIT_APPLIED CREDIT_GRANTED CREDIT_WITHDRAWNinvoiceId or creditNoteId, amountCents, currency, the payment method, the credit balanceAfterCents
CONTACT.DELETED BUSINESS.DELETED DEAL.DELETEDcrm:readid and deleted: true
CRM.IMPORT.COMPLETEDcrm:readbatchId, status (COMPLETE or FAILED) and what it created, updated and could not read
VEHICLES.IMPORT.COMPLETEDvehiclesbatchId, mode (CREATE or UPDATE), status (VALUATING while the valuations run, then COMPLETE) and the counts
VEHICLES.IMPORT.STAGEDvehiclesSince 1.9.0. An import held for review: batchId (read it with GET /api/v1/imports/{batchId}), rows, exceptionRows and the dry run's counts
VEHICLES.IMPORT.APPLIEDvehiclesSince 1.9.0. A staged import run: batchId, the rows it sent, the rows skipped, and the importBatchIds it started (each ends with VEHICLES.IMPORT.COMPLETED)
CAMPAIGN.ACTIVATEDmarketing:readcampaignId: the moment mail will start
PROMOTION.UPDATEDmarketing:readCREATED EDITED PUBLISHED PAUSED ARCHIVED DELETEDpromotionId
PLAN.VEHICLE.UPDATEDplanning:readUNSCHEDULED PARKED UNPARKED TO_OPERATION TRACK_CHANGED DEMOTEDvehicleId, the planVersion it is in, reason, parkedUntil, the new track
PLAN.STRATEGY.UPDATEDplanning:readSET ENDEDstrategy: id and name
BUY.TARGET.UPDATEDbuy-planning:readPAUSED REOPENED CLOSED EDITED REMOVEDtarget, as GET /api/v1/buy-planning/targets/{id} returns it (as it was, for a removed one), and changedFields
BUY.QUARTER.SETbuy-planning:readquarter: periodKey, unitsGoal and budget
INSIGHT.ANSWEREDinsights:readanswer (ACCEPTED, DISMISSED or SKIPPED), answeredBy, side, about and the insight after the answer. Once per answer

PLAN.* events also need Planning on, and BUY.* events Buy planning on, as before. A closing event the buyer hears, and an offer event, carry no vehicle link: the vehicle's record is the seller's.

curl -X POST "$BASE/api/v1/webhooks/subscriptions" \
  -H "x-api-key: $KEY" -H "content-type: application/json" \
  -d '{
    "callback": "https://erp.example.com/cypress",
    "eventTypes": ["MARKETPLACE.OFFER.UPDATED", "CLOSING.UPDATED", "BANKING.UPDATED", "BILLING.UPDATED"]
  }'

Retries & the delivery log

Any non-2xx (or 15s timeout) is retried: 5 attempts, exponential backoff from 10s. After the last failure the delivery parks as DEAD and your registered contacts get an alert email. Nothing is lost — every delivery, its envelope, and your endpoint's recorded response are inspectable at GET /api/v1/deliveries and in the Integrations page of the dashboard, where dead deliveries have a Redeliver button.

The delivery log needs the webhooks scope. The body of a BUY.* delivery is your own target with its prices, so reading it there also needs buy-planning:read (or :write), as subscribing to the event does, and the body of a TASK.* delivery needs tasks:read (or :write) the same way. A key without it still sees the delivery, its status and your endpoint's response; envelope.body is null and bodyWithheld names the scope.

Changed in 1.1.0

A closing recorded as sold off-platform used to send the uncatalogued name CLOSING.PROCESS.COMPLETED, which only subscriptions without an event filter received. It now sends CLOSING.COMPLETED with offPlatform: true in its context. If you filter on CLOSING.COMPLETED, you now hear about off-platform sales too.

The event log, cursor and replay (1.5.0)

Every event a subscription can receive is also kept for 30 days in your organization's event log, in order. Each has a cursor: a number, sent as a string, that grows with every event. The Cypress envelope carries it as cursor (the Manheim envelope does not change).

  • Catch up after downtime. GET /api/v1/events?after=<your last cursor> returns the events since, oldest first (limit up to 500, types to filter). Page with nextCursor while hasMore is true. Omit after to start at the oldest event kept. Planning, Buy planning and task events appear only for a key that may read them, as for subscriptions.
  • Deliver again. POST /api/v1/webhooks/subscriptions/{id}/replay with { "after": "<cursor>" } queues the stored events since that cursor to that one subscription, under their original cursors. Its filters apply.
  • Dedupe on cursor: a replay or a retry carries the same one.
  • Only some records. A subscription can list entityIds (a vehicle, listing, offer or closing id, and for task events the records a task is about) to receive only events about them.

The full event catalog lives in the API reference.