v1.0 โข Lead Management
Complete implementation examples in popular programming languages.
{
"dependencies": {
"express": "^4.18.2",
"crypto": "^1.0.1",
"body-parser": "^1.20.2"
}
}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, () => {
});pip install flask python-dotenv
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
use App\Http\Controllers\WebhookController;
Route::post('/webhook/pollybot', [WebhookController::class, 'handle']);
Route::get('/webhook/health', [WebhookController::class, 'health']);<?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()
]);
}
}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; }
}
}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
# 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")'"
}
}'