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_idis never sent twice to a given endpoint. Customers should still de-dupe onevent_iddefensively (network can deliver duplicates). - Ordering: not guaranteed. Use
occurred_atto 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.soldFires 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_changedFires 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_filedFires 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.