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​
- Go to Settings > Integrations > Webhooks
- Click Add Webhook
- Enter your endpoint URL
- Select events to subscribe to
- 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​
| Event | Description |
|---|---|
lead.created | New lead created |
lead.updated | Lead details updated |
lead.stage_changed | Lead moved to new stage |
lead.assigned | Lead assigned to agent |
lead.won | Lead converted to deal |
lead.lost | Lead marked as lost |
Property Events​
| Event | Description |
|---|---|
property.created | New property created |
property.updated | Property details updated |
property.status_changed | Property status changed |
Unit Events​
| Event | Description |
|---|---|
unit.created | New unit created |
unit.updated | Unit details updated |
unit.sold | Unit marked as sold |
Deal Events​
| Event | Description |
|---|---|
deal.created | New deal created |
deal.closed | Deal completed |
commission.calculated | Commission 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​
| Header | Description |
|---|---|
X-Propertybase-Signature | HMAC-SHA256 signature |
X-Propertybase-Timestamp | Unix 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:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 5 minutes |
| 3 | 30 minutes |
| 4 | 2 hours |
| 5 | 24 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"