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