> ## Documentation Index
> Fetch the complete documentation index at: https://docs.winnerr.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Complete guide to implementing and managing webhooks for real-time event notifications from Winnerr CRM

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

<CardGroup cols={2}>
  <Card title="Real-time Events" icon="bolt">
    Instant notifications for contacts, deals, properties, and communication events
  </Card>

  <Card title="Secure Delivery" icon="shield">
    HTTPS delivery with signature verification and retry mechanisms
  </Card>

  <Card title="Flexible Filtering" icon="filter">
    Subscribe to specific events and filter by conditions
  </Card>

  <Card title="Reliable Processing" icon="check-circle">
    Automatic retries with exponential backoff and dead letter queues
  </Card>
</CardGroup>

## Supported Events

### Contact Events

| Event              | Description                 | Payload                          |
| ------------------ | --------------------------- | -------------------------------- |
| `contact.created`  | New contact created         | Contact object                   |
| `contact.updated`  | Contact information updated | Updated contact object           |
| `contact.deleted`  | Contact deleted             | Contact ID                       |
| `contact.tagged`   | Tags added to contact       | Contact object with new tags     |
| `contact.untagged` | Tags removed from contact   | Contact object with removed tags |
| `contact.assigned` | Contact assigned to agent   | Contact object with assignment   |

### Deal Events

| Event                 | Description                   | Payload                         |
| --------------------- | ----------------------------- | ------------------------------- |
| `deal.created`        | New deal created              | Deal object                     |
| `deal.updated`        | Deal information updated      | Updated deal object             |
| `deal.deleted`        | Deal deleted                  | Deal ID                         |
| `deal.stage_changed`  | Deal moved to different stage | Deal object with stage change   |
| `deal.won`            | Deal marked as won            | Won deal object                 |
| `deal.lost`           | Deal marked as lost           | Lost deal object                |
| `deal.task_created`   | Task added to deal            | Deal object with new task       |
| `deal.task_completed` | Task completed                | Deal object with completed task |

### Property Events

| Event                       | Description                  | Payload                           |
| --------------------------- | ---------------------------- | --------------------------------- |
| `property.created`          | New property listing created | Property object                   |
| `property.updated`          | Property information updated | Updated property object           |
| `property.deleted`          | Property listing deleted     | Property ID                       |
| `property.status_changed`   | Listing status changed       | Property object with status       |
| `property.price_changed`    | List price modified          | Property object with price change |
| `property.photos_uploaded`  | New photos added             | Property object with photo URLs   |
| `property.inquiry_received` | Property inquiry submitted   | Inquiry object                    |

### Communication Events

| Event                              | Description                 | Payload                   |
| ---------------------------------- | --------------------------- | ------------------------- |
| `communication.email_sent`         | Email sent to contact       | Email object              |
| `communication.email_received`     | Email received from contact | Email object              |
| `communication.sms_sent`           | SMS sent to contact         | SMS object                |
| `communication.sms_received`       | SMS received from contact   | SMS object                |
| `communication.call_started`       | Phone call initiated        | Call object               |
| `communication.call_ended`         | Phone call completed        | Call object with duration |
| `communication.voicemail_received` | Voicemail left by contact   | Voicemail object          |

### System Events

| Event                      | Description                       | Payload             |
| -------------------------- | --------------------------------- | ------------------- |
| `organization.updated`     | Organization settings changed     | Organization object |
| `user.created`             | New user added to organization    | User object         |
| `user.updated`             | User information updated          | Updated user object |
| `integration.connected`    | External integration connected    | Integration object  |
| `integration.disconnected` | External integration disconnected | Integration object  |

## Creating Webhooks

### Using the API

Create webhooks programmatically using the Winnerr API:

<CodeGroup>
  ```javascript JavaScript theme={null}
  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);
  ```

  ```python Python theme={null}
  webhook = 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'
  })

  print('Webhook created:', webhook.id)
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.winnerr.com/v1/webhooks \
    -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "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"
    }'
  ```
</CodeGroup>

### Using the Dashboard

You can also create and manage webhooks through the Winnerr dashboard:

1. Navigate to **Settings > Integrations > Webhooks**
2. Click **"Create New Webhook"**
3. Enter your endpoint URL
4. Select the events you want to subscribe to
5. Configure filtering options (optional)
6. Set up authentication and security settings
7. 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:

```javascript theme={null}
// 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'
});
```

### Webhook Headers

Each webhook request includes these headers:

```http theme={null}
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
```

## Webhook Payload Format

### Standard Payload Structure

All webhook payloads follow this consistent structure:

```json theme={null}
{
  "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

#### Contact Created Event

```json theme={null}
{
  "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

```json theme={null}
{
  "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

```json theme={null}
{
  "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:

<CodeGroup>
  ```javascript Node.js/Express theme={null}
  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);
    }
  }
  ```

  ```python Python/Flask theme={null}
  from flask import Flask, request, abort
  import hmac
  import hashlib
  import json
  import time

  app = Flask(__name__)

  @app.route('/webhooks/winnerr', methods=['POST'])
  def handle_webhook():
      signature = request.headers.get('X-Winnerr-Signature', '')
      timestamp = request.headers.get('X-Winnerr-Timestamp', '')
      event_type = request.headers.get('X-Winnerr-Event', '')
      payload = request.get_data()
      
      try:
          # Verify webhook signature
          if not verify_signature(payload, signature, timestamp):
              abort(401)
          
          # Parse event data
          event_data = json.loads(payload)
          
          # Process event based on type
          process_webhook_event(event_data)
          
          return {'received': True}, 200
          
      except Exception as error:
          print(f'Webhook processing error: {error}')
          abort(500)

  def verify_signature(payload, signature, timestamp):
      # Prevent replay attacks
      now = int(time.time())
      if abs(now - int(timestamp)) > 300:
          return False
      
      # Verify signature
      expected_signature = hmac.new(
          WEBHOOK_SECRET.encode(),
          f'{timestamp}.{payload.decode()}'.encode(),
          hashlib.sha256
      ).hexdigest()
      
      return hmac.compare_digest(
          signature.replace('sha256=', ''),
          expected_signature
      )

  def process_webhook_event(event):
      event_type = event['event']
      
      if event_type == 'contact.created':
          handle_contact_created(event['data']['object'])
      elif event_type == 'deal.stage_changed':
          handle_deal_stage_changed(event['data']['object'])
      elif event_type == 'property.price_changed':
          handle_price_changed(event['data']['object'])
      else:
          print(f'Unhandled event type: {event_type}')
  ```

  ```php PHP/Laravel theme={null}
  <?php

  use Illuminate\Http\Request;
  use Illuminate\Support\Facades\Log;

  class WebhookController extends Controller
  {
      public function handleWinnerrWebhook(Request $request)
      {
          $signature = $request->header('X-Winnerr-Signature');
          $timestamp = $request->header('X-Winnerr-Timestamp');
          $eventType = $request->header('X-Winnerr-Event');
          $payload = $request->getContent();
          
          try {
              // Verify webhook signature
              if (!$this->verifySignature($payload, $signature, $timestamp)) {
                  return response()->json(['error' => 'Invalid signature'], 401);
              }
              
              // Parse event data
              $eventData = json_decode($payload, true);
              
              // Process event based on type
              $this->processWebhookEvent($eventData);
              
              return response()->json(['received' => true]);
              
          } catch (Exception $e) {
              Log::error('Webhook processing error: ' . $e->getMessage());
              return response()->json(['error' => 'Processing failed'], 500);
          }
      }
      
      private function verifySignature($payload, $signature, $timestamp)
      {
          // Prevent replay attacks
          $now = time();
          if (abs($now - intval($timestamp)) > 300) {
              return false;
          }
          
          // Verify signature
          $expectedSignature = hash_hmac('sha256', $timestamp . '.' . $payload, config('webhook.secret'));
          
          return hash_equals(
              str_replace('sha256=', '', $signature),
              $expectedSignature
          );
      }
      
      private function processWebhookEvent($event)
      {
          switch ($event['event']) {
              case 'contact.created':
                  $this->handleContactCreated($event['data']['object']);
                  break;
              case 'deal.stage_changed':
                  $this->handleDealStageChanged($event['data']['object']);
                  break;
              case 'property.price_changed':
                  $this->handlePriceChanged($event['data']['object']);
                  break;
              default:
                  Log::info('Unhandled event type: ' . $event['event']);
          }
      }
  }
  ```
</CodeGroup>

### Event-Specific Handlers

Implement specific handlers for different event types:

<CodeGroup>
  ```javascript JavaScript theme={null}
  // 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);
    }
  }
  ```

  ```python Python theme={null}
  # Contact created handler
  async def handle_contact_created(contact):
      print(f'New contact created: {contact["id"]}')
      
      # Add to external CRM
      await add_to_external_crm(contact)
      
      # Send welcome email
      if contact.get('email'):
          await send_welcome_email(contact)
      
      # Create follow-up task
      await create_follow_up_task(contact)
      
      # Update lead scoring
      await update_lead_score(contact)

  # Deal stage changed handler
  async def handle_deal_stage_changed(deal):
      print(f'Deal {deal["id"]} moved to {deal["stage"]}')
      
      # Update external systems
      await update_external_deal(deal)
      
      # Send notifications
      await notify_team_members(deal)
      
      # Trigger stage-specific workflows
      if deal['stage'] == 'CONTRACT':
          await initiate_contract_workflow(deal)
      elif deal['stage'] == 'CLOSING':
          await prepare_closing_documents(deal)
      elif deal['stage'] == 'WON':
          await process_closed_deal(deal)
  ```
</CodeGroup>

## Advanced Webhook Features

### Idempotency Handling

Handle duplicate webhook deliveries gracefully:

```javascript theme={null}
// 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:

```javascript theme={null}
// 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:

```javascript theme={null}
// 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:

<CodeGroup>
  ```javascript JavaScript theme={null}
  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}`);
  });
  ```

  ```python Python theme={null}
  webhooks = client.webhooks.list()

  for webhook in webhooks.data:
      print(f'Webhook {webhook.id}:')
      print(f'  URL: {webhook.url}')
      print(f'  Events: {", ".join(webhook.events)}')
      print(f'  Status: {"Enabled" if webhook.enabled else "Disabled"}')
      print(f'  Created: {webhook.created_at}')
  ```
</CodeGroup>

### Update Webhook

Modify webhook configuration:

<CodeGroup>
  ```javascript JavaScript theme={null}
  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'
  });
  ```

  ```python Python theme={null}
  updated_webhook = 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'
  })
  ```
</CodeGroup>

### Delete Webhook

Remove a webhook configuration:

<CodeGroup>
  ```javascript JavaScript theme={null}
  await client.webhooks.delete('webhook_123');
  console.log('Webhook deleted successfully');
  ```

  ```python Python theme={null}
  client.webhooks.delete('webhook_123')
  print('Webhook deleted successfully')
  ```
</CodeGroup>

## Testing Webhooks

### Webhook Testing Tool

Use the built-in testing tool to verify your webhook endpoint:

```javascript theme={null}
// 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:

```bash theme={null}
# 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:

```javascript theme={null}
// 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:

```javascript theme={null}
// 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:

```javascript theme={null}
// 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:

```javascript theme={null}
// 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();
};
```

### 2. Performance

Optimize webhook processing:

```javascript theme={null}
// 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:

```javascript theme={null}
// 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;
    }
  }
};
```

***

<Note>
  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.
</Note>
