Webhooks allow your application to receive real-time notifications when events occur in Winnerr CRM. Instead of continuously polling the API for changes, webhooks provide an efficient way to stay synchronized with your CRM data.
Webhook Overview
Real-time Events Instant notifications for contacts, deals, properties, and communication events
Secure Delivery HTTPS delivery with signature verification and retry mechanisms
Flexible Filtering Subscribe to specific events and filter by conditions
Reliable Processing Automatic retries with exponential backoff and dead letter queues
Supported Events
Event Description Payload contact.createdNew contact created Contact object contact.updatedContact information updated Updated contact object contact.deletedContact deleted Contact ID contact.taggedTags added to contact Contact object with new tags contact.untaggedTags removed from contact Contact object with removed tags contact.assignedContact assigned to agent Contact object with assignment
Deal Events
Event Description Payload deal.createdNew deal created Deal object deal.updatedDeal information updated Updated deal object deal.deletedDeal deleted Deal ID deal.stage_changedDeal moved to different stage Deal object with stage change deal.wonDeal marked as won Won deal object deal.lostDeal marked as lost Lost deal object deal.task_createdTask added to deal Deal object with new task deal.task_completedTask completed Deal object with completed task
Property Events
Event Description Payload property.createdNew property listing created Property object property.updatedProperty information updated Updated property object property.deletedProperty listing deleted Property ID property.status_changedListing status changed Property object with status property.price_changedList price modified Property object with price change property.photos_uploadedNew photos added Property object with photo URLs property.inquiry_receivedProperty inquiry submitted Inquiry object
Communication Events
Event Description Payload communication.email_sentEmail sent to contact Email object communication.email_receivedEmail received from contact Email object communication.sms_sentSMS sent to contact SMS object communication.sms_receivedSMS received from contact SMS object communication.call_startedPhone call initiated Call object communication.call_endedPhone call completed Call object with duration communication.voicemail_receivedVoicemail left by contact Voicemail object
System Events
Event Description Payload organization.updatedOrganization settings changed Organization object user.createdNew user added to organization User object user.updatedUser information updated Updated user object integration.connectedExternal integration connected Integration object integration.disconnectedExternal integration disconnected Integration object
Creating Webhooks
Using the API
Create webhooks programmatically using the Winnerr API:
const webhook = await client . webhooks . create ({
url: 'https://your-app.com/webhooks/winnerr' ,
events: [
'contact.created' ,
'contact.updated' ,
'deal.stage_changed' ,
'property.status_changed'
],
secret: 'your-webhook-secret-key' ,
enabled: true ,
description: 'Lead management webhook'
});
console . log ( 'Webhook created:' , webhook . id );
Using the Dashboard
You can also create and manage webhooks through the Winnerr dashboard:
Navigate to Settings > Integrations > Webhooks
Click “Create New Webhook”
Enter your endpoint URL
Select the events you want to subscribe to
Configure filtering options (optional)
Set up authentication and security settings
Test the webhook and save
Webhook Configuration
Endpoint Requirements
Your webhook endpoint must meet these requirements:
HTTPS Only : Webhooks are only delivered to HTTPS endpoints
Response Time : Respond within 30 seconds
Status Code : Return 2xx status code for successful processing
Content-Type : Accept application/json content type
Event Filtering
Filter webhooks based on specific conditions:
// Example: Only receive contact events for high-value leads
const webhook = await client . webhooks . create ({
url: 'https://your-app.com/webhooks/high-value-leads' ,
events: [ 'contact.created' , 'contact.updated' ],
filters: {
'contact.leadScore' : { gte: 80 },
'contact.source' : { in: [ 'REFERRAL' , 'WEBSITE' ] }
},
secret: 'your-secret-key'
});
Each webhook request includes these headers:
POST /webhooks/winnerr HTTP / 1.1
Host : your-app.com
Content-Type : application/json
Content-Length : 1234
X-Winnerr-Event : contact.created
X-Winnerr-Signature : sha256=abc123...
X-Winnerr-Delivery : 12345678-1234-1234-1234-123456789abc
X-Winnerr-Timestamp : 1640995200
User-Agent : Winnerr-Webhooks/1.0
Standard Payload Structure
All webhook payloads follow this consistent structure:
{
"id" : "evt_123456789" ,
"event" : "contact.created" ,
"apiVersion" : "2024-01" ,
"createdAt" : "2024-01-15T10:30:00Z" ,
"data" : {
"object" : {
"id" : "contact_123" ,
"firstName" : "John" ,
"lastName" : "Doe" ,
"email" : "john.doe@example.com" ,
"phone" : "+1-555-123-4567" ,
"type" : "LEAD" ,
"status" : "ACTIVE" ,
"leadScore" : 85 ,
"source" : "WEBSITE" ,
"createdAt" : "2024-01-15T10:30:00Z" ,
"updatedAt" : "2024-01-15T10:30:00Z"
},
"previousAttributes" : {},
"changes" : [ "firstName" , "lastName" , "email" ]
},
"organization" : {
"id" : "org_123" ,
"name" : "Premier Realty Group"
},
"user" : {
"id" : "user_456" ,
"name" : "Sarah Johnson" ,
"email" : "sarah@premierrealty.com"
}
}
Event-Specific Payloads
{
"id" : "evt_contact_created_123" ,
"event" : "contact.created" ,
"apiVersion" : "2024-01" ,
"createdAt" : "2024-01-15T10:30:00Z" ,
"data" : {
"object" : {
"id" : "contact_123" ,
"firstName" : "John" ,
"lastName" : "Doe" ,
"email" : "john.doe@example.com" ,
"phone" : "+1-555-123-4567" ,
"type" : "LEAD" ,
"status" : "ACTIVE" ,
"leadScore" : 85 ,
"source" : "WEBSITE" ,
"tags" : [ "website-lead" , "first-time-buyer" ],
"preferences" : {
"propertyTypes" : [ "CONDO" , "TOWNHOUSE" ],
"priceRange" : { "min" : 300000 , "max" : 600000 }
},
"createdAt" : "2024-01-15T10:30:00Z"
},
"previousAttributes" : null ,
"changes" : null
}
}
Deal Stage Changed Event
{
"id" : "evt_deal_stage_123" ,
"event" : "deal.stage_changed" ,
"apiVersion" : "2024-01" ,
"createdAt" : "2024-01-15T14:20:00Z" ,
"data" : {
"object" : {
"id" : "deal_456" ,
"title" : "Miami Waterfront Condo Sale" ,
"stage" : "CONTRACT" ,
"previousStage" : "NEGOTIATION" ,
"amount" : 750000 ,
"probability" : 90 ,
"contactId" : "contact_123" ,
"assignedTo" : "agent_789" ,
"updatedAt" : "2024-01-15T14:20:00Z"
},
"previousAttributes" : {
"stage" : "NEGOTIATION" ,
"probability" : 75
},
"changes" : [ "stage" , "probability" ]
}
}
Property Price Changed Event
{
"id" : "evt_property_price_123" ,
"event" : "property.price_changed" ,
"apiVersion" : "2024-01" ,
"createdAt" : "2024-01-15T16:45:00Z" ,
"data" : {
"object" : {
"id" : "property_789" ,
"mlsId" : "MLS12345678" ,
"address" : {
"street" : "123 Ocean Drive" ,
"city" : "Miami Beach" ,
"state" : "FL" ,
"zipCode" : "33139"
},
"listing" : {
"listPrice" : 725000 ,
"originalPrice" : 795000 ,
"priceReduction" : 70000 ,
"status" : "ACTIVE"
}
},
"previousAttributes" : {
"listing" : {
"listPrice" : 750000
}
},
"changes" : [ "listing.listPrice" ],
"priceChange" : {
"previousPrice" : 750000 ,
"newPrice" : 725000 ,
"changeAmount" : -25000 ,
"changePercentage" : -0.033
}
}
}
Implementing Webhook Handlers
Basic Webhook Handler
Implement a basic webhook handler to process events:
Node.js/Express
Python/Flask
PHP/Laravel
const express = require ( 'express' );
const crypto = require ( 'crypto' );
const app = express ();
// Middleware to capture raw body for signature verification
app . use ( '/webhooks' , express . raw ({ type: 'application/json' }));
app . post ( '/webhooks/winnerr' , ( req , res ) => {
const signature = req . headers [ 'x-winnerr-signature' ];
const timestamp = req . headers [ 'x-winnerr-timestamp' ];
const event = req . headers [ 'x-winnerr-event' ];
const payload = req . body ;
try {
// Verify webhook signature
if ( ! verifySignature ( payload , signature , timestamp )) {
return res . status ( 401 ). json ({ error: 'Invalid signature' });
}
// Parse event data
const eventData = JSON . parse ( payload );
// Process event based on type
processWebhookEvent ( eventData );
// Acknowledge receipt
res . status ( 200 ). json ({ received: true });
} catch ( error ) {
console . error ( 'Webhook processing error:' , error );
res . status ( 500 ). json ({ error: 'Processing failed' });
}
});
function verifySignature ( payload , signature , timestamp ) {
// Prevent replay attacks (reject requests older than 5 minutes)
const now = Math . floor ( Date . now () / 1000 );
if ( Math . abs ( now - timestamp ) > 300 ) {
return false ;
}
// Verify signature
const expectedSignature = crypto
. createHmac ( 'sha256' , process . env . WEBHOOK_SECRET )
. update ( timestamp + '.' + payload )
. digest ( 'hex' );
return crypto . timingSafeEqual (
Buffer . from ( signature . replace ( 'sha256=' , '' )),
Buffer . from ( expectedSignature )
);
}
async function processWebhookEvent ( event ) {
switch ( event . event ) {
case 'contact.created' :
await handleContactCreated ( event . data . object );
break ;
case 'deal.stage_changed' :
await handleDealStageChanged ( event . data . object );
break ;
case 'property.price_changed' :
await handlePriceChanged ( event . data . object );
break ;
default :
console . log ( 'Unhandled event type:' , event . event );
}
}
Event-Specific Handlers
Implement specific handlers for different event types:
// Contact created handler
async function handleContactCreated ( contact ) {
console . log ( 'New contact created:' , contact . id );
// Add to external CRM
await addToExternalCRM ( contact );
// Send welcome email
if ( contact . email ) {
await sendWelcomeEmail ( contact );
}
// Create follow-up task
await createFollowUpTask ( contact );
// Update lead scoring
await updateLeadScore ( contact );
}
// Deal stage changed handler
async function handleDealStageChanged ( deal ) {
console . log ( `Deal ${ deal . id } moved to ${ deal . stage } ` );
// Update external systems
await updateExternalDeal ( deal );
// Send notifications
await notifyTeamMembers ( deal );
// Trigger stage-specific workflows
switch ( deal . stage ) {
case 'CONTRACT' :
await initiateContractWorkflow ( deal );
break ;
case 'CLOSING' :
await prepareClosingDocuments ( deal );
break ;
case 'WON' :
await processClosedDeal ( deal );
break ;
}
}
// Property price changed handler
async function handlePriceChanged ( property ) {
console . log ( `Property ${ property . id } price changed to $ ${ property . listing . listPrice } ` );
// Update property listings on external sites
await updateExternalListings ( property );
// Notify interested clients
await notifyInterestedClients ( property );
// Update market analytics
await updateMarketData ( property );
// Send price alert emails
if ( property . listing . listPrice < property . listing . originalPrice ) {
await sendPriceReductionAlerts ( property );
}
}
Advanced Webhook Features
Idempotency Handling
Handle duplicate webhook deliveries gracefully:
// Idempotency tracking
const processedEvents = new Set ();
async function processWebhookEvent ( event ) {
const eventId = event . id ;
// Check if event already processed
if ( processedEvents . has ( eventId )) {
console . log ( 'Event already processed:' , eventId );
return ;
}
try {
// Process event
await handleEvent ( event );
// Mark as processed
processedEvents . add ( eventId );
// Clean up old events (keep last 1000)
if ( processedEvents . size > 1000 ) {
const eventsArray = Array . from ( processedEvents );
processedEvents . clear ();
eventsArray . slice ( - 500 ). forEach ( id => processedEvents . add ( id ));
}
} catch ( error ) {
console . error ( 'Event processing failed:' , error );
throw error ;
}
}
Webhook Retry Logic
Implement robust retry handling:
// Webhook retry configuration
const RETRY_CONFIG = {
maxRetries: 5 ,
baseDelay: 1000 ,
maxDelay: 30000 ,
backoffMultiplier: 2
};
async function processWithRetry ( operation , eventId ) {
let attempt = 0 ;
while ( attempt < RETRY_CONFIG . maxRetries ) {
try {
await operation ();
return ;
} catch ( error ) {
attempt ++ ;
if ( attempt >= RETRY_CONFIG . maxRetries ) {
// Send to dead letter queue
await sendToDeadLetterQueue ( eventId , error );
throw error ;
}
// Calculate retry delay with exponential backoff
const delay = Math . min (
RETRY_CONFIG . baseDelay * Math . pow ( RETRY_CONFIG . backoffMultiplier , attempt - 1 ),
RETRY_CONFIG . maxDelay
);
console . log ( `Attempt ${ attempt } failed, retrying in ${ delay } ms` );
await new Promise ( resolve => setTimeout ( resolve , delay ));
}
}
}
Webhook Filtering
Filter events on the server side:
// Server-side event filtering
function shouldProcessEvent ( event , filters ) {
if ( ! filters ) return true ;
for ( const [ field , condition ] of Object . entries ( filters )) {
const value = getNestedValue ( event . data . object , field );
if ( ! matchesCondition ( value , condition )) {
return false ;
}
}
return true ;
}
function matchesCondition ( value , condition ) {
if ( typeof condition === 'object' ) {
for ( const [ operator , operand ] of Object . entries ( condition )) {
switch ( operator ) {
case 'eq' :
return value === operand ;
case 'ne' :
return value !== operand ;
case 'gt' :
return value > operand ;
case 'gte' :
return value >= operand ;
case 'lt' :
return value < operand ;
case 'lte' :
return value <= operand ;
case 'in' :
return Array . isArray ( operand ) && operand . includes ( value );
case 'nin' :
return Array . isArray ( operand ) && ! operand . includes ( value );
default :
return false ;
}
}
}
return value === condition ;
}
// Example usage
const filters = {
'leadScore' : { gte: 80 },
'source' : { in: [ 'WEBSITE' , 'REFERRAL' ] },
'type' : { eq: 'LEAD' }
};
if ( shouldProcessEvent ( event , filters )) {
await processEvent ( event );
}
Webhook Management
List Webhooks
Retrieve all configured webhooks:
const webhooks = await client . webhooks . list ();
webhooks . data . forEach ( webhook => {
console . log ( `Webhook ${ webhook . id } :` );
console . log ( ` URL: ${ webhook . url } ` );
console . log ( ` Events: ${ webhook . events . join ( ', ' ) } ` );
console . log ( ` Status: ${ webhook . enabled ? 'Enabled' : 'Disabled' } ` );
console . log ( ` Created: ${ webhook . createdAt } ` );
});
Update Webhook
Modify webhook configuration:
const updatedWebhook = await client . webhooks . update ( 'webhook_123' , {
events: [
'contact.created' ,
'contact.updated' ,
'deal.created' ,
'deal.stage_changed' ,
'property.created'
],
enabled: true ,
description: 'Updated webhook for comprehensive event handling'
});
Delete Webhook
Remove a webhook configuration:
await client . webhooks . delete ( 'webhook_123' );
console . log ( 'Webhook deleted successfully' );
Testing Webhooks
Use the built-in testing tool to verify your webhook endpoint:
// Test webhook endpoint
const testResult = await client . webhooks . test ( 'webhook_123' , {
event: 'contact.created' ,
data: {
id: 'contact_test_123' ,
firstName: 'Test' ,
lastName: 'Contact' ,
email: 'test@example.com'
}
});
console . log ( 'Test result:' , testResult );
Local Development
Use tools like ngrok for local webhook testing:
# Install ngrok
npm install -g ngrok
# Expose local server
ngrok http 3000
# Use the generated URL for webhook endpoint
# https://abc123.ngrok.io/webhooks/winnerr
Webhook Simulator
Create a webhook simulator for testing:
// Webhook simulator for testing
class WebhookSimulator {
constructor ( endpoint , secret ) {
this . endpoint = endpoint ;
this . secret = secret ;
}
async simulateEvent ( eventType , data ) {
const event = {
id: `evt_ ${ Date . now () } ` ,
event: eventType ,
apiVersion: '2024-01' ,
createdAt: new Date (). toISOString (),
data: { object: data },
organization: { id: 'org_test' , name: 'Test Organization' }
};
const payload = JSON . stringify ( event );
const timestamp = Math . floor ( Date . now () / 1000 );
const signature = this . generateSignature ( payload , timestamp );
const response = await fetch ( this . endpoint , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'X-Winnerr-Event' : eventType ,
'X-Winnerr-Signature' : `sha256= ${ signature } ` ,
'X-Winnerr-Timestamp' : timestamp . toString (),
'X-Winnerr-Delivery' : `delivery_ ${ Date . now () } `
},
body: payload
});
return {
status: response . status ,
response: await response . text ()
};
}
generateSignature ( payload , timestamp ) {
const crypto = require ( 'crypto' );
return crypto
. createHmac ( 'sha256' , this . secret )
. update ( ` ${ timestamp } . ${ payload } ` )
. digest ( 'hex' );
}
}
// Usage
const simulator = new WebhookSimulator (
'https://your-app.com/webhooks/winnerr' ,
'your-webhook-secret'
);
const result = await simulator . simulateEvent ( 'contact.created' , {
id: 'contact_test_123' ,
firstName: 'Test' ,
lastName: 'Contact' ,
email: 'test@example.com'
});
console . log ( 'Simulation result:' , result );
Monitoring & Debugging
Webhook Logs
Monitor webhook delivery attempts:
// Get webhook delivery logs
const logs = await client . webhooks . getLogs ( 'webhook_123' , {
startDate: '2024-01-01T00:00:00Z' ,
endDate: '2024-01-31T23:59:59Z' ,
status: 'failed' // or 'success', 'pending'
});
logs . data . forEach ( log => {
console . log ( `Delivery ${ log . id } :` );
console . log ( ` Event: ${ log . event } ` );
console . log ( ` Status: ${ log . status } ` );
console . log ( ` Response: ${ log . responseCode } ` );
console . log ( ` Attempts: ${ log . attempts } ` );
console . log ( ` Next Retry: ${ log . nextRetryAt } ` );
});
Error Handling
Implement comprehensive error logging:
// Webhook error logging
class WebhookLogger {
constructor () {
this . errors = [];
}
logError ( event , error ) {
const logEntry = {
timestamp: new Date (). toISOString (),
eventId: event . id ,
eventType: event . event ,
error: {
message: error . message ,
stack: error . stack ,
code: error . code
},
data: event . data
};
this . errors . push ( logEntry );
// Send to monitoring service
this . sendToMonitoring ( logEntry );
}
async sendToMonitoring ( logEntry ) {
// Send to your monitoring service (e.g., Sentry, DataDog)
try {
await fetch ( 'https://monitoring-service.com/webhook-errors' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ( logEntry )
});
} catch ( error ) {
console . error ( 'Failed to send error to monitoring service:' , error );
}
}
getRecentErrors ( hours = 24 ) {
const cutoff = new Date ( Date . now () - hours * 60 * 60 * 1000 );
return this . errors . filter ( error =>
new Date ( error . timestamp ) > cutoff
);
}
}
const logger = new WebhookLogger ();
// Use in webhook handler
try {
await processWebhookEvent ( event );
} catch ( error ) {
logger . logError ( event , error );
throw error ;
}
Best Practices
1. Security
Implement proper security measures:
// Security best practices
const securityMiddleware = ( req , res , next ) => {
// Rate limiting
if ( isRateLimited ( req . ip )) {
return res . status ( 429 ). json ({ error: 'Rate limit exceeded' });
}
// IP whitelist (optional)
const allowedIPs = process . env . WEBHOOK_ALLOWED_IPS ?. split ( ',' ) || [];
if ( allowedIPs . length > 0 && ! allowedIPs . includes ( req . ip )) {
return res . status ( 403 ). json ({ error: 'IP not allowed' });
}
// Signature verification (handled in main webhook handler)
next ();
};
Optimize webhook processing:
// Asynchronous processing
const queue = require ( 'bull' );
const webhookQueue = new queue ( 'webhook processing' );
app . post ( '/webhooks/winnerr' , async ( req , res ) => {
// Verify signature and respond quickly
if ( ! verifySignature ( req )) {
return res . status ( 401 ). json ({ error: 'Invalid signature' });
}
// Add to processing queue
await webhookQueue . add ( 'process-webhook' , {
event: JSON . parse ( req . body ),
headers: req . headers
});
// Respond immediately
res . status ( 200 ). json ({ received: true });
});
// Process webhooks asynchronously
webhookQueue . process ( 'process-webhook' , async ( job ) => {
const { event , headers } = job . data ;
await processWebhookEvent ( event );
});
3. Reliability
Ensure reliable webhook processing:
// Reliability patterns
const reliableWebhookProcessor = {
// Circuit breaker pattern
circuitBreaker: {
failures: 0 ,
threshold: 5 ,
timeout: 60000 ,
state: 'closed' // closed, open, half-open
},
async processWithCircuitBreaker ( operation ) {
if ( this . circuitBreaker . state === 'open' ) {
if ( Date . now () - this . circuitBreaker . lastFailure > this . circuitBreaker . timeout ) {
this . circuitBreaker . state = 'half-open' ;
} else {
throw new Error ( 'Circuit breaker is open' );
}
}
try {
const result = await operation ();
if ( this . circuitBreaker . state === 'half-open' ) {
this . circuitBreaker . state = 'closed' ;
this . circuitBreaker . failures = 0 ;
}
return result ;
} catch ( error ) {
this . circuitBreaker . failures ++ ;
if ( this . circuitBreaker . failures >= this . circuitBreaker . threshold ) {
this . circuitBreaker . state = 'open' ;
this . circuitBreaker . lastFailure = Date . now ();
}
throw error ;
}
}
};
Webhooks are essential for building responsive real estate applications. Always implement proper security, error handling, and monitoring to ensure reliable webhook processing. Test thoroughly in the sandbox environment before deploying to production.