What are Webhooks?
Webhooks allow you to receive real-time notifications when events occur in your SmartLead campaigns. Instead of polling the API, SmartLead will send HTTP POST requests to your server when events happen.
Use Cases
CRM Integration Update your CRM when leads reply or book meetings
Lead Scoring Score leads based on engagement (opens, clicks)
Notifications Get Slack/Email alerts for important replies
Analytics Send data to your analytics platform
Available Events
Event Description When Triggered EMAIL_SENTEmail sent to lead After successful delivery EMAIL_OPENEDLead opened email When tracking pixel loads EMAIL_CLICKEDLead clicked link When tracked link is clicked EMAIL_REPLIEDLead replied to email When reply is received EMAIL_BOUNCEDEmail bounced When email fails to deliver EMAIL_UNSUBSCRIBEDLead unsubscribed When unsubscribe link clicked
All webhook events follow this structure:
{
"event" : "EMAIL_REPLIED" ,
"timestamp" : "2024-01-15T10:30:00Z" ,
"campaign_id" : 123 ,
"campaign_name" : "Cold Outreach Q1" ,
"lead_id" : 789 ,
"email_account_id" : 456 ,
"lead" : {
"email" : "lead@example.com" ,
"first_name" : "Jane" ,
"last_name" : "Doe" ,
"company_name" : "Acme Corp" ,
"custom_fields" : {
"job_title" : "CEO"
}
},
"sequence_number" : 1 ,
"email" : {
"subject" : "Quick question" ,
"message_id" : "abc123@smartlead.ai"
},
"reply" : {
"subject" : "Re: Quick question" ,
"body" : "Thanks for reaching out..." ,
"received_at" : "2024-01-15T10:30:00Z"
}
}
Setting Up Webhooks
Create Webhook Endpoint
Set up an HTTPS endpoint on your server that accepts POST requests from flask import Flask, request
app = Flask( __name__ )
@app.route ( '/webhook' , methods = [ 'POST' ])
def handle_webhook ():
data = request.json
event = data[ 'event' ]
if event == 'EMAIL_REPLIED' :
# Handle reply
lead_email = data[ 'lead' ][ 'email' ]
reply_body = data[ 'reply' ][ 'body' ]
# Your logic here
return { 'status' : 'success' }, 200
Register Webhook in SmartLead
Use the API to register your webhook URL curl -X POST "https://server.smartlead.ai/api/v1/webhook/create?api_key=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "CRM Integration",
"webhook_url": "https://your-server.com/webhook",
"email_campaign_id": 123,
"association_type": 3,
"event_type_map": {
"EMAIL_REPLIED": true,
"EMAIL_OPENED": true
}
}'
Test Your Webhook
SmartLead will send test events when you save the webhook
Go Live
Activate your campaign and start receiving events
Webhook Association Types
Webhooks can be associated with:
User Level (association_type: 1): Receive events from all campaigns
Client Level (association_type: 2): Events for specific client’s campaigns
Campaign Level (association_type: 3): Events from a single campaign
Event Examples
EMAIL_SENT
{
"event" : "EMAIL_SENT" ,
"timestamp" : "2024-01-15T09:00:00Z" ,
"campaign_id" : 123 ,
"lead_id" : 789 ,
"email_account_id" : 456 ,
"sequence_number" : 1 ,
"lead" : {
"email" : "lead@example.com" ,
"first_name" : "Jane"
}
}
EMAIL_OPENED
{
"event" : "EMAIL_OPENED" ,
"timestamp" : "2024-01-15T10:30:00Z" ,
"campaign_id" : 123 ,
"lead_id" : 789 ,
"sequence_number" : 1 ,
"opened_count" : 3 ,
"first_opened_at" : "2024-01-15T10:30:00Z" ,
"last_opened_at" : "2024-01-15T14:20:00Z"
}
EMAIL_REPLIED
{
"event" : "EMAIL_REPLIED" ,
"timestamp" : "2024-01-15T11:00:00Z" ,
"campaign_id" : 123 ,
"lead_id" : 789 ,
"email_account_id" : 456 ,
"sequence_number" : 1 ,
"reply" : {
"subject" : "Re: Quick question" ,
"body" : "Thanks for reaching out. I'm interested..." ,
"received_at" : "2024-01-15T11:00:00Z" ,
"message_id" : "reply-abc123"
},
"lead" : {
"email" : "lead@example.com" ,
"first_name" : "Jane" ,
"last_name" : "Doe"
}
}
EMAIL_CLICKED
{
"event" : "EMAIL_CLICKED" ,
"timestamp" : "2024-01-15T10:45:00Z" ,
"campaign_id" : 123 ,
"lead_id" : 789 ,
"sequence_number" : 1 ,
"link" : {
"url" : "https://example.com/demo" ,
"clicked_at" : "2024-01-15T10:45:00Z"
}
}
Webhook Security
Verify Webhook Origin
Always verify webhooks come from SmartLead:
import hmac
import hashlib
def verify_webhook ( payload , signature , secret ):
"""Verify webhook signature"""
expected = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
Always use HTTPS for your webhook endpoint to ensure data is encrypted in transit.
Best Practices
Return 200 Quickly : Process webhooks asynchronously
Implement Retry Logic : Handle temporary failures
Validate Payload : Check all required fields exist
Log Everything : Keep webhook logs for debugging
Use Idempotency : Handle duplicate events gracefully
Example Implementations
Node.js/Express
const express = require ( 'express' );
const app = express ();
app . post ( '/webhook' , express . json (), ( req , res ) => {
const { event , lead , reply } = req . body ;
// Quickly acknowledge receipt
res . status ( 200 ). json ({ status: 'received' });
// Process asynchronously
process . nextTick (() => {
switch ( event ) {
case 'EMAIL_REPLIED' :
console . log ( `Reply from ${ lead . email } : ${ reply . body } ` );
// Update your CRM, send notifications, etc.
break ;
case 'EMAIL_OPENED' :
console . log ( ` ${ lead . email } opened email` );
break ;
}
});
});
app . listen ( 3000 );
Python/Flask
from flask import Flask, request
import logging
app = Flask( __name__ )
logger = logging.getLogger( __name__ )
@app.route ( '/webhook' , methods = [ 'POST' ])
def webhook ():
data = request.json
# Log the event
logger.info( f "Received { data[ 'event' ] } event" )
# Quick response
response = { 'status' : 'received' }
# Process asynchronously (use Celery, etc.)
process_webhook_async(data)
return response, 200
def process_webhook_async ( data ):
event = data[ 'event' ]
if event == 'EMAIL_REPLIED' :
# Update CRM
update_crm_contact(
email = data[ 'lead' ][ 'email' ],
status = 'Replied'
)
# Send Slack notification
send_slack_notification(
f "New reply from { data[ 'lead' ][ 'first_name' ] } !"
)
Webhook Management
Create Webhook
curl -X POST "https://server.smartlead.ai/api/v1/webhook/create?api_key=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "My Webhook",
"webhook_url": "https://your-server.com/webhook",
"email_campaign_id": 123,
"association_type": 3,
"event_type_map": {
"EMAIL_SENT": true,
"EMAIL_OPENED": true,
"EMAIL_CLICKED": true,
"EMAIL_REPLIED": true,
"EMAIL_BOUNCED": true
}
}'
Get Webhook Details
curl -X GET "https://server.smartlead.ai/api/v1/webhook/123?api_key=YOUR_API_KEY"
Delete Webhook
curl -X DELETE "https://server.smartlead.ai/api/v1/webhook/delete/123?api_key=YOUR_API_KEY"
Retry Logic
SmartLead will retry failed webhook deliveries:
1st retry : After 1 minute
2nd retry : After 5 minutes
3rd retry : After 15 minutes
4th retry : After 1 hour
5th retry : After 6 hours
After 5 failed attempts, the webhook will be disabled.
Debugging Webhooks
Common Issues
Webhook Not Receiving Events
Check :
URL is publicly accessible
HTTPS is properly configured
Firewall allows SmartLead IPs
Server is returning 200 status code
Events Arriving Out of Order
Solution : Use the timestamp field to order events, not arrival time
Solution : Use idempotency keys (lead_id + event + timestamp)
Reason : Too many failures (5+ consecutive errors)
Solution : Fix your endpoint and re-enable webhook
Test Your Webhook
Use a webhook testing service:
Integration Examples
Update HubSpot
def handle_reply_webhook ( data ):
"""Update HubSpot when lead replies"""
if data[ 'event' ] == 'EMAIL_REPLIED' :
hubspot_contact_id = find_contact_by_email(
data[ 'lead' ][ 'email' ]
)
if hubspot_contact_id:
update_hubspot_contact(
contact_id = hubspot_contact_id,
properties = {
'lead_status' : 'Engaged' ,
'last_activity' : data[ 'timestamp' ],
'reply_text' : data[ 'reply' ][ 'body' ]
}
)
Send Slack Notification
import requests
def send_slack_notification ( data ):
"""Send Slack alert for replies"""
if data[ 'event' ] == 'EMAIL_REPLIED' :
slack_webhook_url = 'YOUR_SLACK_WEBHOOK_URL'
message = {
'text' : f "🎉 New reply from { data[ 'lead' ][ 'first_name' ] } !" ,
'attachments' : [{
'color' : 'good' ,
'fields' : [
{
'title' : 'Lead' ,
'value' : data[ 'lead' ][ 'email' ],
'short' : True
},
{
'title' : 'Campaign' ,
'value' : data[ 'campaign_name' ],
'short' : True
},
{
'title' : 'Reply' ,
'value' : data[ 'reply' ][ 'body' ][: 100 ] + '...' ,
'short' : False
}
]
}]
}
requests.post(slack_webhook_url, json = message)
Rate Limiting
Webhook deliveries are not subject to API rate limits. However, ensure your server can handle:
Burst traffic : Many events arriving simultaneously
Sustained load : Continuous event stream during active campaigns
Use a queue system (Redis, RabbitMQ) to handle webhook events asynchronously.