Dashboard
Back to Webhooks Overview

Webhook Code Examples

Complete implementation examples in popular programming languages.

Available Examples

Node.js
Express.js
Python
Flask
PHP
Laravel
C#
ASP.NET Core

Node.js
Express.js Complete Example

Package Dependencies

{
  "dependencies": {
    "express": "^4.18.2",
    "crypto": "^1.0.1",
    "body-parser": "^1.20.2"
  }
}

Complete Server Implementation

const express = require('express');
const crypto = require('crypto');
const bodyParser = require('body-parser');

const app = express();
const PORT = process.env.PORT || 3000;
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; // Your webhook secret

// Middleware for raw body (needed for signature verification)
app.use('/webhook', bodyParser.raw({ type: 'application/json' }));
app.use(bodyParser.json());

// Signature verification function
function verifyWebhookSignature(body, signature, secret, timestamp) {
  // Check timestamp (prevent replay attacks)
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - timestamp) > 300) { // 5 minutes tolerance
    throw new Error('Request timestamp too old');
  }
  
  // Create payload string (body + timestamp)
  const payload = body + timestamp;
  
  // Generate expected signature
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');
  
  // Compare signatures securely
  const receivedSignature = signature.replace('sha256=', '');
  
  const isValid = crypto.timingSafeEqual(
    Buffer.from(expectedSignature, 'hex'),
    Buffer.from(receivedSignature, 'hex')
  );
  
  if (!isValid) {
    throw new Error('Invalid webhook signature');
  }
  
  return true;
}

// Process lead data
function processLead(leadData) {
  // Add your lead processing logic here:
  // - Save to database
  // - Send notification email
  // - Update CRM
  // - Trigger other workflows
}

// Webhook endpoint
app.post('/webhook/pollybot', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const timestamp = req.headers['x-webhook-timestamp'];
  const body = req.body.toString();
  
  // Log incoming webhook
  console.log('Webhook received at:', new Date().toISOString());
  
  try {
    // Verify signature
    verifyWebhookSignature(body, signature, WEBHOOK_SECRET, timestamp);
    
    // Parse payload
    const payload = JSON.parse(body);
    
    // Handle different event types
    switch (payload.event) {
      case 'LEAD_CREATED':
        processLead(payload.data);
        break;
      
      default:
    }
    
    // Respond with success
    res.status(200).json({ 
      received: true, 
      event: payload.event,
      timestamp: new Date().toISOString()
    });
    
  } catch (error) {
    console.error('Webhook processing failed:', error.message);
    
    // Return appropriate error status
    if (error.message.includes('signature') || error.message.includes('timestamp')) {
      res.status(400).json({ error: 'Invalid request' });
    } else {
      res.status(500).json({ error: 'Processing failed' });
    }
  }
});

// Health check endpoint
app.get('/webhook/health', (req, res) => {
  res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});

app.listen(PORT, () => {
});

Python
Flask Complete Example

Requirements

pip install flask python-dotenv

Complete Server Implementation

import os
import hmac
import hashlib
import time
import json
from flask import Flask, request, jsonify
from dotenv import load_dotenv

# Load environment variables
load_dotenv()

app = Flask(__name__)
WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET')

def verify_webhook_signature(body, signature, secret, timestamp):
    """Verify webhook signature using HMAC SHA256"""
    
    # Check timestamp (prevent replay attacks)
    current_time = int(time.time())
    if abs(current_time - int(timestamp)) > 300:  # 5 minutes tolerance
        raise ValueError("Request timestamp too old")
    
    # Create payload string (body + timestamp)
    payload = body + timestamp
    
    # Generate expected signature
    expected_signature = hmac.new(
        secret.encode('utf-8'),
        payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    
    # Compare signatures securely
    received_signature = signature.replace('sha256=', '')
    
    if not hmac.compare_digest(expected_signature, received_signature):
        raise ValueError("Invalid webhook signature")
    
    return True

def process_lead(lead_data):
    """Process new lead data"""
    print(f"Processing new lead: {lead_data['name']} ({lead_data['email']})")
    
    # Add your lead processing logic here:
    # - Save to database
    # - Send notification email  
    # - Update CRM
    # - Trigger other workflows
    
    # Example: Save to database (pseudo-code)
    # db.leads.insert({
    #     'id': lead_data['id'],
    #     'name': lead_data['name'],
    #     'email': lead_data['email'],
    #     'phone': lead_data.get('phone'),
    #     'chatbot_id': lead_data['chatbotId'],
    #     'created_at': lead_data['createdAt']
    # })

@app.route('/webhook/pollybot', methods=['POST'])
def handle_webhook():
    """Handle incoming webhook from Pollybot"""
    
    # Get headers
    signature = request.headers.get('X-Webhook-Signature')
    timestamp = request.headers.get('X-Webhook-Timestamp')
    
    # Get raw body
    body = request.get_data(as_text=True)
    
    print(f"Webhook received at: {time.strftime('%Y-%m-%d %H:%M:%S')}")
    
    try:
        # Verify signature
        verify_webhook_signature(body, signature, WEBHOOK_SECRET, timestamp)
        
        # Parse payload
        payload = json.loads(body)
        
        # Handle different event types
        if payload['event'] == 'LEAD_CREATED':
            process_lead(payload['data'])
        else:
            print(f"Unknown event type: {payload['event']}")
        
        # Return success response
        return jsonify({
            'received': True,
            'event': payload['event'],
            'timestamp': time.strftime('%Y-%m-%d %H:%M:%S')
        }), 200
        
    except ValueError as e:
        print(f"Webhook verification failed: {e}")
        return jsonify({'error': 'Invalid request'}), 400
    except Exception as e:
        print(f"Webhook processing failed: {e}")
        return jsonify({'error': 'Processing failed'}), 500

@app.route('/webhook/health', methods=['GET'])
def health_check():
    """Health check endpoint"""
    return jsonify({
        'status': 'healthy',
        'timestamp': time.strftime('%Y-%m-%d %H:%M:%S')
    })

if __name__ == '__main__':
    port = int(os.environ.get('PORT', 5000))
    print(f"Webhook server running on port {port}")
    print(f"Webhook URL: http://localhost:{port}/webhook/pollybot")
    app.run(host='0.0.0.0', port=port, debug=False)

PHP
Laravel Complete Example

Route Definition (routes/web.php)

<?php

use App\Http\Controllers\WebhookController;

Route::post('/webhook/pollybot', [WebhookController::class, 'handle']);
Route::get('/webhook/health', [WebhookController::class, 'health']);

Controller Implementation

<?php

namespace App\Http\Controllers;

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

class WebhookController extends Controller
{
    private $webhookSecret;
    
    public function __construct()
    {
        $this->webhookSecret = env('WEBHOOK_SECRET');
    }
    
    /**
     * Verify webhook signature
     */
    private function verifySignature($body, $signature, $timestamp)
    {
        // Check timestamp (prevent replay attacks)
        $now = time();
        if (abs($now - intval($timestamp)) > 300) { // 5 minutes tolerance
            throw new \Exception('Request timestamp too old');
        }
        
        // Create payload string
        $payload = $body . $timestamp;
        
        // Generate expected signature
        $expectedSignature = hash_hmac('sha256', $payload, $this->webhookSecret);
        
        // Compare signatures
        $receivedSignature = str_replace('sha256=', '', $signature);
        
        if (!hash_equals($expectedSignature, $receivedSignature)) {
            throw new \Exception('Invalid webhook signature');
        }
        
        return true;
    }
    
    /**
     * Process lead data
     */
    private function processLead($leadData)
    {
        Log::info('Processing new lead', [
            'lead_id' => $leadData['id'],
            'name' => $leadData['name'],
            'email' => $leadData['email'],
            'chatbot_id' => $leadData['chatbotId']
        ]);
        
        // Add your lead processing logic here:
        // - Save to database using Eloquent models
        // - Send notification email
        // - Update CRM via API
        // - Queue background jobs
        
        // Example: Save to database
        // Lead::create([
        //     'external_id' => $leadData['id'],
        //     'name' => $leadData['name'],
        //     'email' => $leadData['email'],
        //     'phone' => $leadData['phone'] ?? null,
        //     'chatbot_id' => $leadData['chatbotId'],
        //     'metadata' => json_encode($leadData['metadata'] ?? []),
        //     'created_at' => $leadData['createdAt']
        // ]);
    }
    
    /**
     * Handle incoming webhook
     */
    public function handle(Request $request)
    {
        $signature = $request->header('X-Webhook-Signature');
        $timestamp = $request->header('X-Webhook-Timestamp');
        $body = $request->getContent();
        
        Log::info('Webhook received', ['timestamp' => now()]);
        
        try {
            // Verify signature
            $this->verifySignature($body, $signature, $timestamp);
            
            // Parse payload
            $payload = json_decode($body, true);
            
            if (json_last_error() !== JSON_ERROR_NONE) {
                throw new \Exception('Invalid JSON payload');
            }
            
            // Handle different event types
            switch ($payload['event']) {
                case 'LEAD_CREATED':
                    $this->processLead($payload['data']);
                    break;
                    
                default:
                    Log::warning('Unknown event type', ['event' => $payload['event']]);
            }
            
            // Return success response
            return response()->json([
                'received' => true,
                'event' => $payload['event'],
                'timestamp' => now()->toISOString()
            ]);
            
        } catch (\Exception $e) {
            Log::error('Webhook processing failed', [
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);
            
            if (strpos($e->getMessage(), 'signature') !== false || 
                strpos($e->getMessage(), 'timestamp') !== false) {
                return response()->json(['error' => 'Invalid request'], 400);
            }
            
            return response()->json(['error' => 'Processing failed'], 500);
        }
    }
    
    /**
     * Health check endpoint
     */
    public function health()
    {
        return response()->json([
            'status' => 'healthy',
            'timestamp' => now()->toISOString()
        ]);
    }
}

C#
ASP.NET Core Example

Controller Implementation

using Microsoft.AspNetCore.Mvc;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

namespace YourApp.Controllers
{
    [ApiController]
    [Route("webhook")]
    public class WebhookController : ControllerBase
    {
        private readonly ILogger<WebhookController> _logger;
        private readonly string _webhookSecret;
        
        public WebhookController(ILogger<WebhookController> logger, IConfiguration configuration)
        {
            _logger = logger;
            _webhookSecret = configuration["WEBHOOK_SECRET"];
        }
        
        [HttpPost("pollybot")]
        public async Task<IActionResult> HandleWebhook()
        {
            try
            {
                // Get headers
                var signature = Request.Headers["X-Webhook-Signature"].FirstOrDefault();
                var timestamp = Request.Headers["X-Webhook-Timestamp"].FirstOrDefault();
                
                // Read body
                using var reader = new StreamReader(Request.Body);
                var body = await reader.ReadToEndAsync();
                
                _logger.LogInformation("Webhook received at {Timestamp}", DateTime.UtcNow);
                
                // Verify signature
                VerifySignature(body, signature, timestamp);
                
                // Parse payload
                var payload = JsonSerializer.Deserialize<WebhookPayload>(body);
                
                // Handle different event types
                switch (payload.Event)
                {
                    case "LEAD_CREATED":
                        await ProcessLead(payload.Data);
                        break;
                    default:
                        _logger.LogWarning("Unknown event type: {EventType}", payload.Event);
                        break;
                }
                
                return Ok(new
                {
                    received = true,
                    @event = payload.Event,
                    timestamp = DateTime.UtcNow
                });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Webhook processing failed");
                
                if (ex.Message.Contains("signature") || ex.Message.Contains("timestamp"))
                {
                    return BadRequest(new { error = "Invalid request" });
                }
                
                return StatusCode(500, new { error = "Processing failed" });
            }
        }
        
        [HttpGet("health")]
        public IActionResult HealthCheck()
        {
            return Ok(new
            {
                status = "healthy",
                timestamp = DateTime.UtcNow
            });
        }
        
        private void VerifySignature(string body, string signature, string timestamp)
        {
            if (string.IsNullOrEmpty(signature) || string.IsNullOrEmpty(timestamp))
                throw new ArgumentException("Missing required headers");
            
            // Check timestamp
            var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
            var requestTime = long.Parse(timestamp);
            
            if (Math.Abs(now - requestTime) > 300) // 5 minutes tolerance
                throw new ArgumentException("Request timestamp too old");
            
            // Create payload
            var payload = body + timestamp;
            
            // Generate expected signature
            using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(_webhookSecret)))
            {
                var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
                var expectedSignature = Convert.ToHexString(hash).ToLowerInvariant();
                var receivedSignature = signature.Replace("sha256=", "");
                
                if (!CryptographicOperations.FixedTimeEquals(
                    Encoding.UTF8.GetBytes(expectedSignature),
                    Encoding.UTF8.GetBytes(receivedSignature)))
                {
                    throw new ArgumentException("Invalid webhook signature");
                }
            }
        }
        
        private async Task ProcessLead(LeadData leadData)
        {
            _logger.LogInformation("Processing new lead: {Name} ({Email})", 
                leadData.Name, leadData.Email);
            
            // Add your lead processing logic here:
            // - Save to database using Entity Framework
            // - Send notification email
            // - Update CRM
            // - Queue background jobs
        }
    }
    
    public class WebhookPayload
    {
        public string Event { get; set; }
        public long Timestamp { get; set; }
        public LeadData Data { get; set; }
    }
    
    public class LeadData
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
        public string Phone { get; set; }
        public string ChatbotId { get; set; }
        public DateTime CreatedAt { get; set; }
    }
}

Testing Your Implementation

Using Our Test Endpoint First

Before implementing your own endpoint, use our test endpoint to see the exact payload structure:

# Create a webhook with this URL in your settings:
http://localhost:3000/api/test-webhook

# Trigger a lead creation and observe the console output
# Then implement your endpoint based on the observed structure

Manual Testing with cURL

# Test your endpoint with sample data
curl -X POST http://localhost:3000/webhook/pollybot \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Signature: sha256=YOUR_GENERATED_SIGNATURE" \
  -H "X-Webhook-Timestamp: $(date +%s)" \
  -d '{
    "event": "LEAD_CREATED",
    "timestamp": '$(date +%s)',
    "data": {
      "id": "test_lead_123",
      "name": "Test User",
      "email": "test@example.com",
      "phone": "+1234567890",
      "chatbotId": "test_bot_456",
      "metadata": {},
      "createdAt": "'$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")'"
    }
  }'
    PollyBot.ai - Smart Conversations, Seamless Automation