Recipe

Listen for new leases

Availability. Webhooks are on theScale plan and above (Scale and Enterprise). On Starter or Growth, pollGET /v1/leases instead — see pricing.

Problem

You want to do something the moment a lease is signed in the connected PMS — start a billing record, notify a leasing team, kick off a downstream sync — without pollingGET /v1/leases on a timer.

Code

Subscribe to LEASE_SIGNED in Settings → Webhooks, then verify the signature, confirm the event type, and act on the full lease record. LEASE_SIGNEDis a lifecycle event that carries the complete lease (not a diff), so everything you need is in event.data.

Receive LEASE_SIGNED
import hashlib
import hmac
import json
import os

from flask import Flask, request, abort

app = Flask(__name__)
SIGNING_SECRET = os.environ["PROPSOCKET_SIGNING_SECRET"].encode("utf-8")


def verify(raw_body: bytes, signature: str, secret: bytes) -> bool:
    if not signature:
        return False
    expected = hmac.new(secret, raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)


@app.post("/webhooks/propsocket")
def receive():
    raw_body = request.get_data()  # exact bytes — do not re-serialize
    if not verify(raw_body, request.headers.get("X-PropSocket-Signature", ""), SIGNING_SECRET):
        abort(401)

    event = json.loads(raw_body)

    # Subscribe to LEASE_SIGNED in the dashboard, but still guard here:
    # one subscription may carry several event types.
    if event["type"] != "LEASE_SIGNED":
        return ("ignored", 200)

    lease = event["data"]
    upsert_lease(
        lease_id=lease["id"],
        x_id=lease["x_id"],
        property_id=lease["property_id"],
        unit_id=lease["unit_id"],
        # market_rent is integer minor units + currency, e.g. {"amount": 285000, "currency": "USD"}
        market_rent_minor=lease["market_rent"]["amount"],
        currency=lease["market_rent"]["currency"],
        start_date=lease["start_date"],  # "YYYY-MM-DD"
        residents=lease["residents"],
    )
    emit_downstream("lease.created", lease["id"])  # your event bus
    return ("ok", 200)

Why this works

  • LEASE_SIGNED is a lease lifecycle event. Lifecycle events carry the full record, so you don't need a follow-up GET /v1/leases/{id} to enrich it.
  • Verifying HMAC-SHA256 over the raw body proves the event came from PropSocket. The signature mechanics are in Webhooks → Verify the signature.
  • Money arrives as integer minor units plus a currency ({ "amount": 285000, "currency": "USD" } = $2,850.00). Store the integer; never parse it into a float.

What to watch out for

  • One subscription can deliver several event types. Guard onevent.type in code even if you only ticked LEASE_SIGNED in the dashboard — it keeps the handler correct if someone widens the subscription later.
  • Delivery is at-least-once. The same LEASE_SIGNED can arrive twice after a retry. Make upsert_lease idempotent on the lease id, or dedupe on the event id per Dedupe webhooks idempotently.
  • Acknowledge fast. Return a 2xx within 10 seconds, then do the real work asynchronously. A slow handler triggers retries and duplicate processing.
  • Initial sync is silent. Leases that already existed when you connected the source arrive during the bulk load with no per-record events — onlyFIRST_SYNC_COMPLETE. To capture those, run a one-timebackfill.

Next

To pull in leases that predate your subscription, runBackfill after downtime.