Skip to main content
Webhooks require a Business tier subscription.

Overview

Borough webhooks notify you when a listing changes, delivered within minutes of Borough detecting the change on its regular refresh cycle (not the instant it changes upstream). Webhooks use the Standard Webhooks specification for payload signing and verification. Detection sets the pace, and two things can slow it:
  • Price events on detailed listings lag. For a listing Borough has already fetched in detail (detailScraped: true), the regular refresh does not re-read its price. A price change is detected only when Borough re-checks that listing’s detail page, which can be days to weeks later, so listing.price_decreased and listing.price_increased for those listings can arrive well after the change. See Price freshness.
  • Delivery is throughput-limited. Borough publishes up to 100 detected changes every 5 minutes (about 1,200 an hour). After a refresh that detects a large batch of changes, the backlog is delivered over hours rather than minutes.

Event types

Delivery scope

Subscriptions are matched on event type only. There is no per-subscription area, price, or listing filter: an active subscription receives every change of a subscribed type across the whole Borough dataset, so subscribing to listing.created means every new listing Borough indexes, citywide. Two ways to narrow that down:
  • Filter in your handler. Every payload carries data.listingId (see below); ignore the ones you do not care about.
  • Use a watcher. POST /v1/watchers scopes monitoring to a single listing, a single building, or one saved search, and emits changes for that target only. Watchers deliver on their own SSE stream, never to your webhook URL, and they are not a general-purpose filter: a Business customer can have at most 5 active listing watchers (polling at most hourly), and a saved-search watcher only accepts filters that match 500 or fewer active listings. For anything broader, filter webhook events in your handler.

Payload format

timestamp is when Borough detected the change, in UTC, formatted YYYY-MM-DD HH:MM:SS with no T and no zone designator. Some parsers (including JavaScript’s Date) read that format as local time, so parse it explicitly as UTC, for example new Date(timestamp.replace(' ', 'T') + 'Z'). The time the delivery was signed is in the webhook-timestamp header.

Signature verification

Every webhook request includes three headers: The signature is computed as:
where key is your subscription secret with the whsec_ prefix removed and the remainder base64-decoded, per the Standard Webhooks specification. Using the literal secret string as the key produces a signature that never matches.

Verify with the SDK

The SDK’s webhook helpers rely on the standardwebhooks package, an optional peer dependency. Install it alongside the SDK:
Without it, verification throws, the helper answers 400, and Borough keeps retrying the delivery until it is dead-lettered.

Retry schedule

Failed deliveries (non-2xx responses, timeouts, and connection errors) are retried up to 7 times with exponentially increasing delays (10s, 30s, 90s, 4.5m, 13.5m, 40.5m, then 1h), so a delivery is attempted over roughly two hours before it is given up on. After all retries are exhausted, the delivery is moved to a dead letter queue. During a brief maintenance window, deliveries are parked and re-attempted rather than dropped. Parked attempts still count against the 7-retry budget, so an extended pause can send a delivery to the dead letter queue. Treat the retry budget as the ceiling in both cases.

Delivery semantics

  • Respond within 10 seconds. Each delivery attempt times out after 10 seconds, and a timeout is retried like any other failure. Return a 2xx quickly and do slow work afterwards.
  • Expect duplicates. Delivery is at-least-once: after a maintenance pause or a recovery re-send, the same event can arrive more than once. Deduplicate on the webhook-id header, which stays the same for every copy of one event sent to one subscription.
  • Pausing or deleting stops queued deliveries. Deliveries still in the queue for a subscription you pause or delete are cancelled rather than sent.
  • Webhooks need an active Business subscription. If your subscription drops below Business, Borough deactivates your webhook subscriptions automatically.

Managing subscriptions

Use the Webhook CRUD endpoints to create, list, update, and delete subscriptions.