Skip to main content
Webhooks require a Business tier subscription.

Overview

Borough uses the Standard Webhooks specification for signing webhook payloads. The SDK provides middleware helpers for Express and Next.js to verify signatures and parse events.

Installation

The webhook helpers require the standardwebhooks peer dependency:

Express

The SDK types event.type as string and event.data as unknown, so the examples cast data to the payload shape.

Next.js (App Router)

In both helpers, an error thrown by your handler produces a 400 response, which Borough treats as a failed delivery and retries. Make handlers idempotent.

Event types

A listing going off-market always sends listing.expired, never listing.status_changed.

Event payload

All events share the same shape:
timestamp is when Borough detected the change, in UTC, but it has no Z or offset. Parse it as UTC: new Date(timestamp.replace(" ", "T") + "Z"). Passing it to new Date() unchanged reads it as local time.

Delivery timing

Events arrive after Borough detects the change, not when it happened:
  • Borough checks for new changes to deliver every 5 minutes and handles up to 100 changes per run across all subscribers, so a large batch (for example, many listings expiring at once) can take hours to drain.
  • New, expired, and relisted listings are detected as Borough’s regular listing updates run: rentals about every 6 hours, sales about every 8 hours.
  • Price changes on listings whose full detail Borough already holds are detected only when that detail is refreshed: in Borough’s periodic re-check, or when a paid customer’s detail request triggers a refresh. Those events can arrive days or weeks after the change. On-demand refreshes draw on a shared live-fetch budget, so while that budget is held for Borough’s scheduled updates, or live fetching is paused, these price events wait for the periodic re-check.

Manual verification

If you’re not using Express or Next.js, verify the signature manually:

Webhook secret

Your webhook secret is returned only when you create a subscription. GET /v1/webhooks (borough.webhooks.list()) does not return it, so store it at creation time. It starts with whsec_ and should be stored securely (e.g., environment variable). Each subscription has its own unique secret.

Retry behavior

Failed deliveries (non-2xx responses) are retried up to 7 times with increasing delays. Ensure your handler returns a 2xx status within 10 seconds to acknowledge receipt. After all retries are exhausted, the delivery is moved to a dead letter queue and cannot be replayed through the API. Delivery is at-least-once, so the same event can arrive more than once. Deduplicate on the webhook-id header, which stays the same for every attempt at delivering one event to one subscription. If your plan drops below Business, Borough deactivates your subscriptions. Upgrading again does not turn them back on: re-enable each one with a PATCH setting active: true.