Skip to main content

Webhooks

Receive real-time notifications when events occur in PropertyBase.

Overview​

Webhooks allow your application to receive HTTP callbacks when events happen in your PropertyBase workspace. Instead of polling the API, webhooks push data to your server instantly.

Setting Up Webhooks​

Via Dashboard​

  1. Go to Settings > Integrations > Webhooks
  2. Click Add Webhook
  3. Enter your endpoint URL
  4. Select events to subscribe to
  5. Save

Via API​

curl -X POST "https://api.propertybase.ai/v1/webhooks" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhooks/propertybase",
"events": ["lead.created", "lead.stage_changed", "deal.closed"]
}'

Event Types​

Lead Events​

EventDescription
lead.createdNew lead created
lead.updatedLead details updated
lead.stage_changedLead moved to new stage
lead.assignedLead assigned to agent
lead.wonLead converted to deal
lead.lostLead marked as lost

Property Events​

EventDescription
property.createdNew property created
property.updatedProperty details updated
property.status_changedProperty status changed

Unit Events​

EventDescription
unit.createdNew unit created
unit.updatedUnit details updated
unit.soldUnit marked as sold

Deal Events​

EventDescription
deal.createdNew deal created
deal.closedDeal completed
commission.calculatedCommission computed

Webhook Payload​

All webhooks follow this structure:

{
"id": "evt_abc123def456",
"event": "lead.created",
"timestamp": "2024-01-25T14:30:00Z",
"workspace_id": "ws_123",
"data": {
"id": "lead_456",
"name": "John Smith",
"email": "john@example.com",
"source": "website",
"stage": "new",
"created_at": "2024-01-25T14:30:00Z"
}
}

Verifying Webhooks​

Always verify webhook signatures to ensure authenticity.

Headers​

HeaderDescription
X-Propertybase-SignatureHMAC-SHA256 signature
X-Propertybase-TimestampUnix timestamp

Verification Code​

import crypto from 'crypto';

function verifyWebhook(
payload: string,
signature: string,
timestamp: string,
secret: string
): boolean {
// Check timestamp is recent (within 5 minutes)
const now = Math.floor(Date.now() / 1000);
const webhookTime = parseInt(timestamp);
if (Math.abs(now - webhookTime) > 300) {
return false; // Replay attack prevention
}

// Verify signature
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');

return crypto.timingSafeEqual(
Buffer.from(signature.replace('sha256=', '')),
Buffer.from(expectedSignature)
);
}

Express.js Example​

import express from 'express';

const app = express();
app.use(express.json({ verify: (req, res, buf) => {
(req as any).rawBody = buf.toString();
}}));

app.post('/webhooks/propertybase', (req, res) => {
const signature = req.headers['x-propertybase-signature'] as string;
const timestamp = req.headers['x-propertybase-timestamp'] as string;

if (!verifyWebhook((req as any).rawBody, signature, timestamp, WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}

const event = req.body;

// Process event asynchronously
processEvent(event).catch(console.error);

// Respond quickly
res.status(200).json({ received: true });
});

async function processEvent(event: any) {
switch (event.event) {
case 'lead.created':
await handleNewLead(event.data);
break;
case 'deal.closed':
await handleClosedDeal(event.data);
break;
}
}

Best Practices​

Respond Quickly​

Return a 2xx response within 5 seconds:

// Good - process async
app.post('/webhooks', (req, res) => {
queue.add(req.body); // Add to queue
res.status(200).send('OK'); // Respond immediately
});

// Bad - process sync
app.post('/webhooks', async (req, res) => {
await processEvent(req.body); // Takes 10+ seconds
res.status(200).send('OK'); // Too late!
});

Handle Duplicates​

Use event IDs for idempotency:

const processedEvents = new Set();

async function handleWebhook(event: any) {
if (processedEvents.has(event.id)) {
return; // Already processed
}

processedEvents.add(event.id);
await processEvent(event);
}

Implement Retries​

If your endpoint fails, we retry:

AttemptDelay
1Immediate
25 minutes
330 minutes
42 hours
524 hours

After 5 failures, the webhook is disabled.

Testing Webhooks​

Test Endpoint​

Send a test event:

curl -X POST "https://api.propertybase.ai/v1/webhooks/webhook_123/test" \
-H "Authorization: Bearer YOUR_API_KEY"

Local Development​

Use ngrok or similar for local testing:

ngrok http 3000
# Use the HTTPS URL as your webhook endpoint

Troubleshooting​

Common Issues​

Webhook not receiving events

  • Check endpoint URL is correct
  • Verify webhook is active
  • Check your server logs

Invalid signature errors

  • Ensure you're using the raw request body
  • Check the secret is correct
  • Verify timestamp tolerance

Timeouts

  • Process events asynchronously
  • Return response before processing
  • Use a queue for heavy operations

Viewing Deliveries​

Check delivery history:

curl -X GET "https://api.propertybase.ai/v1/webhooks/webhook_123/deliveries" \
-H "Authorization: Bearer YOUR_API_KEY"