Skip to main content

Facebook Lead Generation & Webhooks

Facebook Lead Ads allow businesses to collect leads directly on Facebook without redirecting users to external websites. The integration uses webhooks to receive real-time notifications when leads are submitted.

Overview

Lead Ads Architecture

sequenceDiagram
participant User as Facebook User
participant FB as Facebook
participant Webhook as DashClicks Webhook
participant API as DashClicks API
participant DB as MongoDB

User->>FB: Fills out Lead Form
FB->>FB: Stores lead data
FB->>Webhook: POST /webhooks/facebook/leads
Webhook->>FB: GET /{leadgen_id}
FB->>Webhook: Lead data
Webhook->>DB: Store lead
Webhook->>API: Process lead
API->>DB: Update campaign data

Integration Source

Lead generation uses the facebook-leads source:

{
source: 'facebook-leads',
scopes: [
'leads_retrieval',
'pages_read_engagement',
'pages_manage_ads'
]
}

Webhook Setup

Step 1: Create Campaign

Create a DashClicks campaign for lead tracking:

{
_id: ObjectId("507f1f77bcf86cd799439011"),
account_id: ObjectId("507f191e810c19729de860ea"),
integration: 'facebook_ads',
page_id: '123456789012345', // Facebook Page ID
form_id: '555555555', // Lead Form ID
is_deleted: false
}

Step 2: Register Webhook

Endpoint: POST /v1/e/facebook/accounts/webhook

Request Body:

{
"campaign_id": "507f1f77bcf86cd799439011"
}

Process:

  1. Retrieve campaign data to get page_id
  2. Get Facebook access token
  3. Get page-specific access token
  4. Install app to page
  5. Create/update webhook subscription
  6. Store webhook record in database

Implementation:

exports.createWebhook = async (req, res, next) => {
try {
const appAccessToken = await fbProvider.getAppAccessToken();
let accountId = await checkAccountAccess(req);
let owner = req.auth.uid.toString();
let campaignId = req.body.campaign_id;

// Webhook configuration
let reqBody = {
object: 'page',
callback_url: `${process.env.FACEBOOK_ADS_WEBHOOK_URL}`,
fields: 'leadgen',
include_values: true,
verify_token: `${process.env.FACEBOOK_VERIFY_TOKEN}`,
};

// Get campaign data
let campaignData = await campaignModel.find({ _id: campaignId }).lean().exec();

if (!campaignData || !campaignData[0]) {
return res.status(200).json({ success: true });
}

campaignData = campaignData[0];

// Get Facebook token
const existKeys = await facebookModel.find({
accountID: accountId.toString(),
source: 'facebook-leads',
});

let accessToken = await facebookModel.checkAccessTokenValidity({
req: req,
expirationTime: existKeys.token.expires_in,
accessToken: existKeys.token.access_token,
docId: existKeys.docId,
source: 'facebook-leads',
});

// Get page access token
let pageAccessToken = await fbProvider.getPageAccessToken(
accessToken,
campaignData.page_id,
'access_token,name,id',
);

// Install app to page
await fbProvider.installApptoPage(campaignData.page_id, pageAccessToken.access_token);

// Create webhook subscription
await fbProvider.createWebhook(appAccessToken.access_token, reqBody);

// Save webhook record
let fbwebhookData = {
account_id: accountId,
owner: owner,
webhook_url: reqBody.callback_url,
};

const readwebHookData = await new fbwebhookModel(fbwebhookData).save();

return res.status(200).json({
success: true,
data: readwebHookData,
});
} catch (error) {
if (error?.response?.data?.error?.message) {
error.additional_info = error.response.data.error.message;
} else if (error?.message) {
error.additional_info = error.message;
}
error.message = 'ERROR_CREATING_WEBHOOK';
next(error);
}
};

Facebook API Calls:

// 1. Get page access token
exports.getPageAccessToken = async (app_access_token, pageId, fields) => {
const url = `https://graph.facebook.com/${process.env.FACEBOOK_API_VERSION}/${pageId}?fields=${fields}&access_token=${app_access_token}`;
const result = await axios({ method: 'GET', url });
return result.data;
};

// 2. Install app to page
exports.installApptoPage = async (pageId, page_access_token) => {
const url = `https://graph.facebook.com/${pageId}/subscribed_apps?subscribed_fields=leadgen&access_token=${page_access_token}`;
const result = await axios({ method: 'POST', url });
return result.data;
};

// 3. Create webhook subscription
exports.createWebhook = async (app_access_token, requestBody) => {
const url = `https://graph.facebook.com/${process.env.FACEBOOK_API_VERSION}/${process.env.FACEBOOK_CLIENT_ID}/subscriptions`;
const options = {
method: 'POST',
headers: { Authorization: `Bearer ${app_access_token}` },
params: requestBody,
url,
};
const result = await axios(options);
return result.data;
};

Step 3: Webhook Verification

When Facebook first contacts your webhook, it sends a verification request:

Request:

GET /webhooks/facebook/leads?hub.mode=subscribe&hub.challenge=1234567890&hub.verify_token=your_verify_token

Expected Response:

HTTP/1.1 200 OK
Content-Type: text/plain

1234567890

Implementation:

app.get('/webhooks/facebook/leads', (req, res) => {
const mode = req.query['hub.mode'];
const token = req.query['hub.verify_token'];
const challenge = req.query['hub.challenge'];

if (mode && token) {
if (mode === 'subscribe' && token === process.env.FACEBOOK_VERIFY_TOKEN) {
console.log('Webhook verified');
res.status(200).send(challenge);
} else {
res.sendStatus(403);
}
}
});

Webhook Events

Lead Submission Event

When a user submits a lead form, Facebook sends:

Request:

POST /webhooks/facebook/leads
Content-Type: application/json
X-Hub-Signature: sha1=abc123...

{
"object": "page",
"entry": [
{
"id": "123456789012345",
"time": 1704844800,
"changes": [
{
"field": "leadgen",
"value": {
"leadgen_id": "987654321",
"page_id": "123456789012345",
"form_id": "555555555",
"adgroup_id": "444444444",
"ad_id": "333333333",
"created_time": 1704844800
}
}
]
}
]
}

Event Fields:

const webhookFields = {
object: 'Always "page"',
entry: [
{
id: 'Page ID',
time: 'Unix timestamp',
changes: [
{
field: 'Always "leadgen"',
value: {
leadgen_id: 'Lead ID to retrieve data',
page_id: 'Facebook Page ID',
form_id: 'Lead Form ID',
adgroup_id: 'Ad Set ID (optional)',
ad_id: 'Ad ID (optional)',
created_time: 'Lead creation timestamp',
},
},
],
},
],
};

Signature Verification

Facebook signs webhook requests using HMAC-SHA1:

const crypto = require('crypto');

function verifySignature(req, res, next) {
const signature = req.headers['x-hub-signature'];

if (!signature) {
return res.sendStatus(403);
}

const elements = signature.split('=');
const signatureHash = elements[1];

const expectedHash = crypto
.createHmac('sha1', process.env.FACEBOOK_APP_SECRET)
.update(JSON.stringify(req.body))
.digest('hex');

if (signatureHash !== expectedHash) {
return res.sendStatus(403);
}

next();
}

Retrieving Lead Data

Get Lead Details

After receiving webhook notification, retrieve full lead data:

Facebook API Call:

exports.getLeadData = async (leadId, access_token) => {
const url = `https://graph.facebook.com/${process.env.FACEBOOK_API_VERSION}/${leadId}`;
const options = {
method: 'GET',
headers: { Authorization: `Bearer ${access_token}` },
url,
};
const result = await axios(options);
return result.data;
};

Response:

{
"id": "987654321",
"created_time": "2024-01-15T14:30:00+0000",
"field_data": [
{
"name": "full_name",
"values": ["John Doe"]
},
{
"name": "email",
"values": ["john.doe@example.com"]
},
{
"name": "phone_number",
"values": ["+1234567890"]
},
{
"name": "custom_question_1",
"values": ["Product Demo"]
}
]
}

Lead Data Structure

const leadData = {
id: 'Lead ID',
created_time: 'ISO 8601 timestamp',
field_data: [
{
name: 'Field name (e.g., full_name, email)',
values: ['Array of values (usually single item)'],
},
],
};

Standard Fields:

const standardFields = [
'full_name',
'first_name',
'last_name',
'email',
'phone_number',
'zip_code',
'city',
'state',
'country',
'date_of_birth',
'gender',
'post_code',
'street_address',
'work_email',
'work_phone_number',
'company_name',
'job_title',
];

Lead Form Management

List Lead Forms

Get all lead forms for a Facebook Page:

Endpoint: GET /v1/e/facebook/accounts/:pageid/leadforms

Query Parameters:

{
pageaccesstoken: 'Page-specific access token (required)',
account_id: 'DashClicks sub-account ID (optional)'
}

Response:

{
"success": true,
"message": "SUCCESS",
"data": [
{
"id": "555555555",
"name": "Contact Us Form",
"status": "ACTIVE",
"locale": "en_US",
"questions": [
{
"key": "FULL_NAME",
"label": "Full Name",
"type": "FULL_NAME"
},
{
"key": "EMAIL",
"label": "Email",
"type": "EMAIL"
},
{
"key": "PHONE_NUMBER",
"label": "Phone",
"type": "PHONE_NUMBER"
}
],
"privacy_policy": {
"url": "https://example.com/privacy",
"link_text": "Privacy Policy"
},
"created_time": "2024-01-15T10:30:00+0000",
"page_id": "123456789012345"
}
]
}

Campaign Management

Disconnect Lead Integration

When disconnecting the facebook-leads source:

Endpoint: DELETE /v1/e/facebook/auth/delete?source=facebook-leads

Process:

  1. Soft-delete Facebook token
  2. Disable all lead campaigns
  3. Send notifications

Implementation:

if (source === 'facebook-leads') {
// Disable all Facebook Ads campaigns
await campaignModel.updateMany(
{
account_id: accountID,
is_deleted: false,
integration: 'facebook_ads',
},
{ $set: { is_deleted: true } },
);
}

// Soft-delete token
await facebookModel.update(
{ account_id: accountID, source: source },
{ $set: { deleted: new Date() } },
);

Data Collections

campaign-data

Stores DashClicks lead campaigns:

{
_id: ObjectId,
account_id: ObjectId,
integration: 'facebook_ads',
page_id: String, // Facebook Page ID
form_id: String, // Lead Form ID
is_deleted: Boolean,
created_at: Date
}

facebook-webhooks

Stores webhook registrations:

{
_id: ObjectId,
account_id: ObjectId,
owner: ObjectId,
webhook_url: String // Webhook callback URL
}

integrations.facebookads.key

Stores OAuth tokens with source: 'facebook-leads':

{
account_id: ObjectId,
created_by: ObjectId,
source: 'facebook-leads',
token: {
access_token: String,
token_type: 'bearer',
expires_in: Number
},
deleted: Date
}

Environment Configuration

Required Variables

# OAuth
FACEBOOK_CLIENT_ID=your_app_id
FACEBOOK_CLIENT_SECRET=your_app_secret
FACEBOOK_API_VERSION=v18.0

# Webhooks
FACEBOOK_ADS_WEBHOOK_URL=https://api.dashclicks.com/webhooks/facebook/leads
FACEBOOK_VERIFY_TOKEN=your_webhook_verification_token
FACEBOOK_APP_SECRET=your_app_secret

Error Handling

Common Errors

// Missing campaign
{
"success": true // Returns success if campaign not found
}

// Missing token
{
"success": false,
"errno": 400,
"message": "Record Not found!"
}

// Webhook creation error
{
"success": false,
"errno": 400,
"message": "ERROR_CREATING_WEBHOOK",
"additional_info": "App not installed on page"
}

// Invalid page access token
{
"error": {
"message": "Invalid OAuth access token",
"type": "OAuthException",
"code": 190
}
}

Integration Checklist

Setup

  • Connect Facebook with facebook-leads source
  • Create Facebook Page
  • Create Lead Ad form on Facebook
  • Configure webhook URL in Facebook App settings
  • Set webhook verification token
  • Create DashClicks campaign
  • Register webhook for campaign

Testing

  • Test webhook verification
  • Submit test lead through Facebook form
  • Verify webhook receives notification
  • Test lead data retrieval
  • Verify signature validation
  • Test error handling

Production

  • Configure production webhook URL
  • Set up webhook monitoring
  • Implement lead processing queue
  • Add lead deduplication
  • Set up alerting for webhook failures
  • Monitor token expiration

Best Practices

Webhook Reliability

  1. Respond Quickly: Return 200 status within 5 seconds
  2. Process Async: Queue lead processing for background handling
  3. Retry Logic: Facebook retries failed webhooks
  4. Idempotency: Handle duplicate webhook deliveries

Lead Processing

// Example webhook handler
app.post('/webhooks/facebook/leads', verifySignature, (req, res) => {
// Respond immediately
res.status(200).send('EVENT_RECEIVED');

// Process async
const { entry } = req.body;
entry.forEach(item => {
item.changes.forEach(change => {
if (change.field === 'leadgen') {
queueLeadProcessing(change.value);
}
});
});
});

Security

  1. Verify Signatures: Always validate X-Hub-Signature
  2. Use HTTPS: Webhooks must use HTTPS
  3. Rotate Tokens: Regularly refresh access tokens
  4. Validate Data: Sanitize lead data before storage

Additional Resources

💬

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:30 AM