How to Receive Webhooks
Webhook guide for SMS, SMS Flash, and Voice campaigns
How to Receive LigueLead Webhooks
Configure your application to receive real-time webhook notifications for SMS, SMS Flash, and Voice campaign status changes.
Overview
LigueLead automatically sends webhooks to notify your application when campaign statuses change. This applies to all channels: SMS, SMS Flash, and Voice. Webhooks enable you to:
- Monitor in real-time the status of your deliveries and calls
- Implement retry logic for delivery or call failures
- Keep synchronization between your system and LigueLead
- Collect metrics on delivery, call performance, and credits consumed
Webhook URL Configuration
Client Area Access
- Access https://areadocliente.liguelead.app.br/
- Log in with your credentials
- Navigate to Integrations > API Token
- Locate the "Webhook URL" section
- Enter the complete URL of your webhook endpoint
- Click "Save"
A single webhook URL receives notifications for all channels (SMS, SMS Flash, and Voice). You cannot configure different URLs per channel.
URL Requirements
Your webhook URL must meet these requirements:
| Requirement | Details |
|---|---|
| Protocol | HTTPS (required for production) |
| Response time | Must respond within 5 seconds |
| Status code | Return HTTP 200 to confirm receipt |
| Availability | Must be always available to receive notifications |
Valid URL example:
https://api.mycompany.com/webhooks/ligueleadWebhook Payload Structure
All LigueLead webhooks — regardless of channel — share the same top-level JSON structure and a set of common fields inside the campaign object.
Common Fields
These fields are present in every webhook payload:
| Field | Type | Description |
|---|---|---|
event | string | Event type (always "campaign.status") |
app_id | string | Your application ID in LigueLead |
occurred_at | string | Event date/time in ISO 8601 format |
campaign.id | string | Unique campaign ID (UUID v4) |
campaign.type | string | Campaign type: "sms" or "voice" |
campaign.source | string | Source of the campaign (e.g., "api", "n8n", "make") |
campaign.phone | string | Recipient phone number in international format |
campaign.credits_required | number | Number of credits consumed by this message or call |
campaign.sent_at | string | Date when the campaign was initiated (ISO 8601 format) |
campaign.status | string | Current campaign status (see channel-specific statuses below) |
Channel-Specific Payload and Statuses
Each channel includes additional fields and its own set of statuses. Select the tab below for your channel.
SMS / SMS Flash Payload Example
{
"event": "campaign.status",
"app_id": "your-app-id-here",
"occurred_at": "2026-02-02T12:00:15.400Z",
"campaign": {
"id": "campaign-uuid-v4",
"type": "sms",
"source": "api",
"phone": "+5513991884678",
"message": "Check out our latest offers! Visit our website now.",
"is_flash": false,
"credits_required": 1,
"sent_at": "2026-02-02",
"status": "delivered"
}
}SMS-Specific Fields
These fields appear only in SMS and SMS Flash webhooks, in addition to the common fields:
| Field | Type | Description |
|---|---|---|
campaign.message | string | The SMS message content sent to the recipient |
campaign.is_flash | boolean | true if the message was sent as Flash SMS, false for standard SMS |
SMS and SMS Flash share the same payload structure. The only difference is the
is_flashfield:truefor Flash SMS,falsefor standard SMS.
SMS Campaign Statuses
| Status | Description |
|---|---|
sent | SMS was queued and sent to the carrier |
delivered | SMS was delivered to the recipient |
undelivered | SMS was not delivered (includes timeout and carrier rejection) |
failed | SMS failed (error, congestion, no route, or unknown) |
graph TD
A[SMS Initiated] --> B[sent]
B --> C{Delivery Result}
C --> D[delivered]
C --> E[undelivered]
C --> F[invalid_number]
C --> G[failed]Endpoint Implementation
The webhook endpoint structure is the same for all channels. The difference is in the fields you validate and the statuses you handle. Select the channel tab, then the language tab for a complete example.
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhooks/liguelead', (req, res) => {
try {
const payload = req.body;
// Validate payload structure
if (!isValidPayload(payload)) {
return res.status(400).json({ error: 'Invalid payload' });
}
// Process webhook based on status
processWebhook(payload);
// Respond immediately to avoid 5-second timeout
res.status(200).json({ received: true });
} catch (error) {
console.error('Webhook processing error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
function isValidPayload(payload) {
const requiredFields = ['event', 'app_id', 'occurred_at'];
const campaignFields = [
'id', 'type', 'status', 'source', 'phone',
'message', 'is_flash', 'credits_required', 'sent_at'
];
return requiredFields.every(field => payload.hasOwnProperty(field))
&& payload.campaign
&& campaignFields.every(field => payload.campaign.hasOwnProperty(field));
}
function processWebhook(payload) {
const { campaign, occurred_at } = payload;
const { id: campaign_id, type: campaign_type, status } = campaign;
console.log(`Campaign ${campaign_id} (${campaign_type}): ${status} at ${occurred_at}`);
switch (status) {
case 'delivered':
handleDelivered(payload);
break;
case 'undelivered':
handleUndelivered(payload);
break;
case 'invalid_number':
handleInvalidNumber(payload);
break;
case 'failed':
handleFailed(payload);
break;
case 'sent':
handleSent(payload);
break;
default:
console.warn(`Unknown status: ${status}`);
}
}
function handleDelivered(payload) {
// Update status in database
// Send notification to user
// Trigger next workflow action
}
function handleUndelivered(payload) {
// Implement retry logic
// Notify about failure
}
function handleInvalidNumber(payload) {
// Remove or flag the number in your contact list
}
function handleFailed(payload) {
// Implement retry logic
// Notify about failure
// Update error metrics
}
function handleSent(payload) {
// Update campaign status in your system
}
app.listen(3000, () => console.log('Webhook server running on port 3000'));Error Handling
Retry Behavior
LigueLead does NOT implement automatic retry for webhooks. If your endpoint does not respond within 5 seconds, returns a non-success status, or is unavailable, the notification will be permanently lost.
To prevent data loss:
- Respond immediately — return HTTP 200 before doing heavy processing
- Use asynchronous processing — offload database writes, API calls, and notifications to a background queue
- Keep detailed logs — log every incoming webhook for debugging and reconciliation
- Monitor endpoint availability — set up uptime monitoring and alerts for your webhook URL
LigueLead Log Examples
On successful dispatch:
{
"message": "Webhook notification dispatched successfully",
"url": "https://your-url.com/webhook",
"method": "POST",
"payload": { /* webhook payload */ }
}On failed dispatch:
{
"message": "Failed to dispatch webhook notification",
"url": "https://your-url.com/webhook",
"error": "timeout of 5000ms exceeded",
"stack": "..."
}Security and Best Practices
1. Origin Validation
LigueLead does not currently implement HMAC signatures. Validate the request origin and payload structure:
// Validate origin IP (if LigueLead provides a static IP range)
const allowedIPs = ['LIGUELEAD_IP'];
if (!allowedIPs.includes(req.ip)) {
return res.status(403).json({ error: 'Forbidden' });
}
// Validate expected event type
if (payload.event !== 'campaign.status') {
return res.status(400).json({ error: 'Invalid event' });
}2. Idempotency
Prevent duplicate processing by tracking a unique identifier per webhook:
const processedWebhooks = new Set();
function processWebhook(payload) {
// Unique key: campaign ID + status + timestamp
const webhookId = `${payload.campaign.id}_${payload.campaign.status}_${payload.occurred_at}`;
if (processedWebhooks.has(webhookId)) {
console.log('Webhook already processed:', webhookId);
return;
}
processedWebhooks.add(webhookId);
// Process webhook...
}In production, replace the in-memory
Setwith a persistent store (e.g., Redis or a database table) to survive server restarts.
3. Rate Limiting
Protect your endpoint from excessive requests:
const rateLimit = require('express-rate-limit');
const webhookLimiter = rateLimit({
windowMs: 1 * 60 * 1000, // 1-minute window
max: 100, // Maximum 100 requests per minute
message: 'Too many webhooks'
});
app.use('/webhooks/liguelead', webhookLimiter);Monitoring and Debug
Webhook Headers
LigueLead sends the following headers with every webhook request:
Content-Type: application/json
User-Agent: LigueLead-WebhookDispatcher/1.0Recommended Logging
Log incoming webhooks with channel-relevant fields:
console.log('Webhook received:', {
campaign_id: payload.campaign.id,
campaign_type: payload.campaign.type,
status: payload.campaign.status,
message: payload.campaign.message,
is_flash: payload.campaign.is_flash,
timestamp: new Date().toISOString(),
processing_time_ms: processingTime
});Important Metrics
Monitor these metrics across all channels:
- Success rate of received webhooks (HTTP 200 responses)
- Response time of your endpoint (target < 1 second)
- Status distribution across campaigns (delivered vs. failed, answered vs. no_answer)
- Webhook frequency per campaign and channel
Testing Webhooks
1. Development Environment
Use ngrok to expose your local server to the internet:
npm install -g ngrok
ngrok http 3000Copy the generated HTTPS URL (e.g., https://abc123.ngrok.io) and paste it into the Webhook URL field in the LigueLead client area.
2. Payload Simulation
Send a test webhook to your local endpoint to verify your implementation:
const testPayload = {
"event": "campaign.status",
"app_id": "test-app-id",
"occurred_at": new Date().toISOString(),
"campaign": {
"id": "test-campaign-123",
"type": "sms",
"source": "api",
"phone": "+5511999999999",
"message": "Check out our latest offers!",
"is_flash": false,
"credits_required": 1,
"sent_at": new Date().toISOString().split('T')[0],
"status": "delivered"
}
};
fetch('http://localhost:3000/webhooks/liguelead', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(testPayload)
});Frequently Asked Questions
How often are webhooks sent?
Webhooks are sent immediately when a campaign status changes, for all channels (SMS, SMS Flash, and Voice).
What if my endpoint is down?
LigueLead does not store or resend webhooks. If your endpoint is unavailable, the notification is permanently lost. High availability is strongly recommended.
Can I configure different URLs for different campaign types?
No. A single webhook URL receives notifications for all channels (SMS, SMS Flash, and Voice). Use the campaign.type field to route processing logic.
How do I identify duplicate webhooks?
Use the combination of campaign.id + campaign.status + occurred_at as a unique identifier to detect and skip duplicates.
Is there a payload or frequency limit?
There is no specific payload size limit. Webhook frequency depends on your campaign volume.
How do I distinguish SMS from SMS Flash in the payload?
Both use campaign.type: "sms". Check the campaign.is_flash field: true for Flash SMS, false for standard SMS.
How do I distinguish SMS from Voice webhooks?
Check the campaign.type field: "sms" for SMS/SMS Flash campaigns, "voice" for Voice campaigns. Each type includes different channel-specific fields.
Support
For questions or issues with webhooks:
- 🌐 Portal: https://areadocliente.liguelead.app.br/
This documentation was updated in March 2026. For the latest version, always consult the API portal.
Updated 17 days ago
