Skip to main content
The Marketplace API is in a private beta. It answers only for accounts Investorlift has enabled. Access says how to ask for one. A route shape on these pages can change before the beta ends. The changelog records every change.
A write on Mogul or through the API emits an event. You read events in two ways. A webhook endpoint gets each event pushed and signed. The events feed serves the same events in sequence order. Use both: the webhook for speed, the feed for replay.

Endpoints

Create an endpoint with POST /sell/webhooks or POST /buy/webhooks under the scope webhooks:manage. On the sell side, an owner or an admin creates it. The body names the URL and the event types. Each side holds ten endpoints, and the eleventh answers 409 webhook_limit. GET lists the endpoints with the creator, the client and the last delivery. PATCH /sell/webhooks/{webhook_id} changes the event types, pauses the endpoint or starts it again. rotate_secret: true in the same body mints a new signing secret, shown once. DELETE removes one. The /buy/ side has the same routes. POST /sell/webhooks/{webhook_id}/test sends a ping event to that endpoint alone, and POST /buy/webhooks/{webhook_id}/test does the same on the buy side. The test answers 202 with data: { endpoint_id, event_id, type }, and type reads ping. Read the delivery in last_delivery on the next read of the endpoint.
  • The URL is https only, on a public host, with no user name or password. The API resolves the host at create and again before every delivery. A host that resolves to a private, loopback, link-local or metadata address answers 422 webhook_url_refused at create, and takes no delivery later. The API follows no redirect: a 3xx is a failed delivery.
  • Before the endpoint is active, the API sends a challenge to the URL and expects it echoed. Until then the endpoint reads pending_verification. The other states are paused and disabled.
  • The create response shows the signing secret once. It starts with whsec_. Store it. Every later read shows its last four characters only. The secret rotates through PATCH with rotate_secret: true. During a rotation a delivery carries the old and the new signature.
  • The endpoint pins api_version at create. Every delivery to it renders the event at that version. A payload change ships as a new version, and you opt into it with a new endpoint.
  • The endpoint records its creator and client. When the creator leaves the organization or revokes your application, the endpoint pauses. The owner of the organization then gets an email to confirm or delete it within 7 days.
  • A paused endpoint names the cause in paused_reason: creator_left, consent_revoked or client_disabled. An owner or an admin starts it again with status set to active.

The signed delivery

Every delivery is a POST with the content type application/json, the User-Agent Investorlift-Marketplace-Webhooks/1, and the three headers of the Standard Webhooks specification: Verify before you parse. Compute the HMAC over the exact bytes of the body. Compare with a constant-time comparison. Refuse a timestamp more than 5 minutes from your clock. Answer 2xx inside 15 seconds, and do the work after.

Delivery

Delivery is at least once and unordered. The event id is your dedupe key: store it, and skip a delivery whose id you hold. A delivery that gets no 2xx inside 15 seconds is a failure, and so is a 3xx. The API retries a failure five times over about four minutes with backoff, then once an hour. An endpoint that fails for 3 days is disabled: the API emails the owner and emits webhook.disabled. The API keeps a payload 7 days for redelivery, then keeps its hash.

The event shape

One event has one shape on the feed and on a webhook:

The catalogue

The event types are a closed list for each side. An endpoint takes the types of its own side and the two compliance types, and no other type. The OpenAPI document of this API lists each event type in its webhooks section, with the three headers and the event shape. The sell side. The owner is the organization. The buy side. The owner is the buyer. Compliance, both sides. buyer.redacted and lead.redacted fire on account deletion or a verified deletion request. Each carries one id and nothing else. On buyer.redacted, delete the buyer’s contact from your systems within 10 business days and keep the id as a tombstone (Terms). deal.matched carries deal_id, city, state, zip, asking_price, deal_type and matched_buy_box_id, and no other deal field. The full read is GET /buy/deals/{deal_id} against the deal budget, and the delivery itself counts one deal row.

What an event says about a buyer

A seller-side event about an offer, an inquiry, an address request or a lead carries buyer: { buyer_id, name, entity_name }. The fields email, phone (one number) and phone_type ride only when the creator of the endpoint holds contacts:read, an owner or an admin. On the feed, the token that reads must hold it. A token without it gets no such key: absent, never null. Each row with email or phone counts against the daily contact-bearing row cap (Trust and limits). A delivery past that cap carries no contact members, and data.contact.status reads budget_reached. No event carries a score, a rank, a breakdown or a channel of a recommended lead. lead.created carries a consent block with source, captured_at, text_version and sms_opt_in. You are the sender under the TCPA and CAN-SPAM for anything you do with the contact (Terms). A new organization is one with fewer than five verified deals or under 90 days of standing. It gets no email or phone on an API-published deal until Investorlift verifies the deal. Such a row carries contact: { status: "pending_verification" }.

The events feed

GET /sell/events and GET /buy/events serve the events of the side under the scope events:read, in sequence order, in the shape above. The query takes four parameters:
  • cursor=, the page.next_cursor of the page before this one
  • types=, a comma list of event types
  • from=, an RFC 3339 instant for a first read
  • limit=, the page size, 1 to 100, 50 by default
Events stay 30 days. The feed lags 5 seconds behind now, so a late commit never lands behind your cursor. The cursor is a sequence position bound to your account, not to the filter: change types= and keep the cursor. A cursor from another account answers 400 invalid_parameter. Read the feed again without a cursor.

Replay

1

Store the id and the instant of every event you handle

From a webhook, store id and occurred_at from the body. From the feed, also store page.next_cursor.
2

Poll the feed with the cursor

Send GET /sell/events?cursor=... from your last cursor. Handle every event whose id you do not hold. Follow next_cursor until it is null.
3

After a gap or an outage, start from an instant

Send from= with the occurred_at of your last handled event, inside 30 days. Skip the ids you hold. Beyond 30 days, read the resources with updated_since= instead.
Identifiers and the envelope has the list cursor. What the API returns has the recommended-lead rule.