API · Webhooks

Webhooks

PropRaven POSTs signed JSON to your endpoint when watched parcels change. Three event types are live today. Deliveries are HMAC-signed, retried on failure, and idempotent on event_id.

Delivery semantics

  • Transport: HTTPS POST, Content-Type: application/json. Any 2xx response acks the delivery.
  • Retries: non-2xx responses are retried with exponential backoff up to your retry budget. After exhaustion, deliveries dead-letter and surface in the deliveries dashboard for manual replay.
  • Idempotency: the same event_id is never sent twice to a given endpoint. Customers should still de-dupe on event_id defensively (network can deliver duplicates).
  • Ordering: not guaranteed. Use occurred_at to order events for a given parcel.
  • Quotas: Starter 5 endpoints · 10K events/day · Pro 50 endpoints · 100K events/day · Scale unlimited.

Signature format

Every delivery carries an X-PropRaven-Signature header in Stripe-style format:

X-PropRaven-Signature: t=1715882400000,v1=abc123…

t is the unix millisecond timestamp PropRaven generated the signature. v1 is the hex-encoded HMAC-SHA256 of <t>.<rawBody> using the secret returned at endpoint-creation time.

Reject any request where the timestamp is more than 5 minutes from your clock — this prevents signature replay. Compare signatures in constant time.

Verifier helpers in every SDK:

Register an endpoint

Create a webhook via POST /v1/webhooks:

curl -X POST https://api.propraven.com/v1/webhooks \
  -H "Authorization: Bearer $PROPRAVEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/propraven",
    "event_types": ["parcel.sold", "parcel.permit_filed"],
    "filter_kind": "state_fips",
    "filter_value": { "state_fips": "06" }
  }'

Response includes a secret shown once — persist it before discarding the response. It's the signing key for verification.

Filter kinds:

  • parcel_ids — array of up to 1,000 specific parcels. Most precise; lowest event volume.
  • state_fips — every event matching a given state.
  • county_fips — state + county pair.

Event types

Every payload includes the common envelope below plus the event-specific fields shown on each card.

Common envelope

event_idstringDeterministic — use for idempotency. Same event_id is never delivered twice to a given endpoint.
event_typestringThe event name (constant per row below).
occurred_atdate-timeISO-8601 UTC. When the underlying state change happened.
delivery_attemptintegerStarts at 1; increments on every retry.
parcel_idstringComposite county_fips:parcel_id.
state_fipsstring2-digit US state FIPS code.
county_fipsstring3-digit county FIPS (within state).
source_run_idstring?PropRaven ingest run that surfaced this event.
parcel.sold

Fires when a deed is recorded against a watched parcel AND the transfer is a real sale — sale_price > 0 and arm's-length. Sub-case of parcel.owner_changed.

Payload fields

sale_datedate?Date of sale, YYYY-MM-DD.
sale_price_usdnumber?May be null in non-disclosure states (KS, MS, TX, UT, WY, etc.).
grantorstring?Seller name as recorded.
granteestring?Buyer name as recorded.
recorded_datedate?Date the deed was recorded with the county.
is_arm_lengthboolean?True when the transaction is arm's-length per upstream classification.
document_typestring?Deed classification — Warranty Deed, Quitclaim, Trust Transfer, etc.
parcel.owner_changed

Fires when ANY deed is recorded against a watched parcel — sales, quitclaims, gift deeds, trust transfers. Superset of parcel.sold.

Payload fields

recorded_datedate?Date the deed was recorded with the county.
document_typestring?Deed classification.
document_numberstring?Recorder document number.
prior_ownerstring?Grantor on the recorded deed.
new_ownerstring?Grantee on the recorded deed.
is_saleboolean?True when this transfer is also a sale (parcel.sold also fires).
parcel.permit_filed

Fires when a new permit is matched to a watched parcel. Fires once per permit_id per parcel; jurisdiction re-postings de-dupe.

Payload fields

permit_idstringPropRaven internal permit id; stable across re-ingests.
permit_numberstring?Jurisdiction-issued permit number.
permit_typestring?Building, electrical, roofing, demolition, etc.
permit_statusstring?Filed, issued, in_review, final, expired, withdrawn.
filed_datedate?Date of filing.
issued_datedate?Date the permit was issued (if applicable).
descriptionstring?Free-form description of the work.
estimated_costnumber?Declared job cost in USD.
contractor_namestring?Contractor as recorded.
contractor_licensestring?Contractor license number.
applicant_namestring?Applicant as recorded.
jurisdiction_idstring?PropRaven jurisdiction id; join to /v1/jurisdictions.

Managing endpoints

# List your endpoints
curl https://api.propraven.com/v1/webhooks -H "Authorization: Bearer $PROPRAVEN_API_KEY"

# Disable an endpoint (delete = soft-disable; already-queued deliveries keep retrying)
curl -X DELETE https://api.propraven.com/v1/webhooks/wh_... -H "Authorization: Bearer $PROPRAVEN_API_KEY"

# Pull recent delivery attempts for an endpoint
curl https://api.propraven.com/v1/webhooks/wh_.../deliveries -H "Authorization: Bearer $PROPRAVEN_API_KEY"

Or use the webhook settings page for a UI view of endpoints, recent deliveries, and one-click replay.

Full reference

Every field above is also documented in the OpenAPI 3.1 spec under the webhooks: block at api.propraven.com/openapi.json. Stainless regenerates the SDKs on every spec change so the helpers above always match.