Create webhook subscription

Registers an endpoint to receive lead-activity payloads. Everything below
describes how those deliveries behave.

Verifying a delivery

A webhook with a secret configured receives three headers:

  • x-lp-signature — HMAC-SHA256 hex digest (64 hex characters)
  • x-lp-timestamp — Unix ms timestamp used in the signature
  • x-lp-signature-version — currently always v1

Each delivery is signed with that webhook's own secret, not a shared key.
A webhook registered without a secret is delivered unsigned, with none of
these headers
, and nothing else on the request authenticates it — establish
trust another way (IP allow-listing, mTLS, an unguessable path) or configure a
secret.

Signing recipe:

stringToSign = timestamp + url + JSON.stringify(payload)
signature    = HMAC_SHA256(stringToSign, secret).hex()

Where url is the destination exactly as registered and payload is the
delivered body. Hash the raw bytes you received — re-serializing a parsed
object can reorder keys and break the signature even when the data is
identical. Compare with a timing-safe comparison, and validate the digest is
64 hex characters before decoding.

Custom headers you register are merged with the LP-managed ones. Any header
using the x-lp prefix, or colliding with the signature headers,
x-lp-delivery-id or Content-Type, is dropped — a custom header can never
spoof these.

Duplicate deliveries

Delivery is at-least-once. The same payload can arrive more than once, both
from retries and because a failure to reach any one of your webhooks re-queues
the whole event for every endpoint.

Every delivery carries x-lp-delivery-id, a 64-character lowercase hex string
that is stable across retries and redeliveries of the same event to the
same webhook, and distinct per webhook and per payload. Use it as an
idempotency key: record ids you have finished processing, and return 2xx
without re-doing the work when one repeats.

Track claimed and finished separately. Marking an id handled before the work
succeeds means a crash mid-processing looks like a duplicate on retry — you ack,
we stop retrying, and the event is lost silently. Give the claim a lease so a
crashed attempt is retried rather than stranded.

Do not use the signature as an idempotency key: x-lp-timestamp is
regenerated per attempt and is part of the signed string, so two deliveries of
one event carry different signatures. x-lp-delivery-id is not part of the
signed string and is never proof of origin — treat it strictly as a
deduplication hint.

Two payloads that are byte-for-byte identical, delivered to the same webhook,
share one id. Nothing distinguishes them, so treating the second as a duplicate
is intended.

Field presence

Only activityType and activitySourceUrl are guaranteed. Every other
field is optional and may be absent on any given delivery
— fields are
omitted, never sent empty, so treat absent and empty as different and do not
key your handler on a field always being present.

This applies in particular to activityAction, platform, leadState,
isNewLead, newPhoneNumber and formCustomFields. Absence carries no
meaning: a missing isNewLead or newPhoneNumber is not equivalent to
false.

Some fields describe the lead record rather than the activity —
leadFirstName, leadLastName, leadPhoneNumber, leadSource,
statusType, companyName, leadTags, assignedAgents and
assignedAgent. These reflect the lead record at the time of delivery
rather than as of the activity
. If a lead's status or assigned agents changed
in between, you receive the newer values. Do not use them to reconstruct what
the lead looked like when the activity happened.

leadEmail and leadId are present only for activity that identifies a
lead. Anonymous browsing — VIEW_LISTING above all — arrives with neither.

Which surface the activity came from

When present, platform is one of mobile, web or unknown.

Only mobile is exact: the activity originated in the Luxury Presence mobile
app. web is the default for activity received through services that
serve both surfaces, so it is not a guarantee of browser origin — some web
activity originates on mobile. unknown means the surface could not be
determined.

Segment on platform: "mobile" where certainty is required. web is not
its complement, and a missing platform must not be read as web.

MLS and address enrichment (activityListingMlsId, activityListingAddress,
activityListingCity, activityListingState, activityListingZip and
meta.property) is populated only for CONTACT_INQUIRY submitted through a
contact form, and activityListingMlsId only where that listing carries an
MLS key. Other listing-scoped activity carries activityListingId alone.

Which activity types you receive

Filtering on the events array is best-effort, so a webhook can receive
activity types it did not subscribe to — VIEW_LISTING most of all, which is
by a wide margin the highest-volume type. Filter on activityType in your
handler rather than relying on the subscription.

Recent Requests
Log in to see full request history
TimeStatusUser Agent
Retrieving recent requests…
LoadingLoading…
Body Params
string
required

The webhook name

string
required

The endpoint URL where webhook events will be sent

events
array of strings
required

The activity types to subscribe to. Accepts any value of the activity-type vocabulary listed on this schema's activityType / activityAction fields, or 'ALL' as a wildcard. Filtering by this field is best-effort: a webhook may receive activity types it did not subscribe to. Filter on activityType in your handler rather than relying on this field.

events*
string

Secret that will be used to sign requests with. If not provided, requests sent will be unsigned.

headers
object

Custom headers to include in webhook requests

boolean
Defaults to true

Whether the webhook is active

Responses

400

Invalid webhook data

Language
LoadingLoading…
Response
Click Try It! to start a request and see the response here! Or choose an example:
application/json