Webhooks
Overview
Section titled “Overview”Borough webhooks deliver real-time notifications when listings change. Webhooks use the Standard Webhooks specification for payload signing and verification.
Event types
Section titled “Event types”| Event | Description |
|---|---|
listing.created | A new listing appeared in the search index |
listing.price_decreased | Listing price dropped by more than $25 |
listing.price_increased | Listing price increased by more than $25 |
listing.status_changed | Listing status changed (e.g., ACTIVE to IN_CONTRACT) |
listing.expired | Listing went off-market |
Payload format
Section titled “Payload format”{ "type": "listing.price_decreased", "data": { "listingId": "4961849", "oldValue": "5222", "newValue": "4900" }, "timestamp": "2026-02-15T14:30:00Z"}Signature verification
Section titled “Signature verification”Every webhook request includes three headers:
| Header | Description |
|---|---|
webhook-id | Unique delivery ID |
webhook-timestamp | Unix timestamp of signing |
webhook-signature | v1,<base64-hmac-sha256> |
The signature is computed as:
HMAC-SHA256(secret, "${webhook-id}.${webhook-timestamp}.${body}")Verify with the SDK
Section titled “Verify with the SDK”import { webhookMiddleware } from '@borough/sdk/webhooks/express';
app.post('/webhooks/borough',express.raw({ type: 'application/json' }),webhookMiddleware(process.env.BOROUGH_WEBHOOK_SECRET, async (event) => {console.log('Received:', event.type, event.data);}));import { webhookHandler } from '@borough/sdk/webhooks/nextjs';
export const POST = webhookHandler( process.env.BOROUGH_WEBHOOK_SECRET!, async (event) => { console.log('Received:', event.type, event.data); });Retry schedule
Section titled “Retry schedule”Failed deliveries (non-2xx responses) are retried up to 7 times with increasing delays. After all retries are exhausted, the delivery is moved to a dead letter queue.
Managing subscriptions
Section titled “Managing subscriptions”Use the Webhook CRUD endpoints to create, list, update, and delete subscriptions.