Marketing API Version

Retrieving Leads

There are three ways to read leads: TSV download, bulk-read, and with Webhooks. For special cases where lead ads forms are not sufficient, you could use continued flow.

Permissions

To read leads or get realtime updates, you need an Page admin's access token for the page with the ad's promoted object.

You can also add people with Page roles to a whitelist for downloading leads.

curl \
-F 'user_id=<USER_ID>' \
"https://graph.facebook.com///leadgen_whitelisted_users (https://graph.facebook.com/%3CAPI_VERSION%3E/%3CPAGE_ID%3E/leadgen_whitelisted_users)"

See Lead Ads Whitelist.

TSV Download

You can access links for TSV download at {page_id}\leadgen_forms. Note the field is labeled csv although the only supported format is TSV. Or you can directly query a lead generation form:

use FacebookAds\Object\LeadgenForm;

$form = new LeadgenForm(<FORM_ID>);
$form->read();
from facebookads.adobjects.leadgenform import LeadgenForm

form = LeadgenForm(<LEADGEN_FORM_ID>)
form.remote_read()
curl -G \
  -d 'access_token=<ACCESS_TOKEN>' \
  https://graph.facebook.com/v2.8/<FORM_ID>

Response:

{
  "id": "<LEAD_GEN_FORM_ID>",
  "leadgen_export_csv_url": "https://www.facebook.com/ads/lead_gen/export_csv?type=form&id=<FORM_ID>",
  "locale": "en_US",
  "name": "My Form"
}

Filtering

Optionally you can filter the URL response to download leads for a specified date range. Use from_date and to_date in a POSIX or UNIX time format, expressing the number of seconds since epoch. For example, to download leads for the time period starting 2016-01-13 18 (tel:2016011318):20:31 UTC and ending 2016-01-14 18 (tel:2016011418):20:31 UTC:

https://www.facebook.com/ads/lead_gen/export_csv/?id=&type=form&from_date=1482698431&to_date=1482784831 (https://www.facebook.com/ads/lead_gen/export_csv/?id=%3CFORM_ID%3E&type=form&from_date=1482698431&to_date=1482784831)

Note:

  • If from_date not set, or is a value less than the form creation time, the form creation time is used.
  • If to_date not set, or is a timestamp greater than the present time, current time is used.

If any entries lack Ad IDs or Adgroup IDs in the TSV, it may be due to the following reasons: * The lead is generated from organic reach. is_organic in the TSV displays 1. Otherwise the value is 0. * The lead may be submitted from an Ad Preview * Anyone downloading leads lacks Advertiser privileges on the Ad Account running the Lead ad.

Bulk Read

The leads exists on both ad group and form nodes. This returns all data associated with their respective objects. Because a form can be re-used for many ads, your form could contain far more leads than an ad using it.

To read in-bulk by ad:

use FacebookAds\Object\Ad;

$ad = new Ad(<AD_ID>);
$leads = $ad->getLeads();
from facebookads.adobjects.ad import Ad

ad = Ad(<AD_ID>)
leads = ad.get_leads()
APINodeList<Lead> leads = new Ad(<AD_ID>, context).getLeads()
  .execute();
curl -G \
  -d 'access_token=<ACCESS_TOKEN>' \
  https://graph.facebook.com/v2.8/<AD_ID>/leads

To read by form:

use FacebookAds\Object\LeadgenForm;

$form = new LeadgenForm(<FORM_ID>);
$leads = $form->getLeads();
from facebookads.adobjects.leadgenform import LeadgenForm

form = LeadgenForm(<LEADGEN_FORM_ID>)
leads = form.get_leads()
curl -G \
  -d 'access_token=<ACCESS_TOKEN>' \
  https://graph.facebook.com/v2.8/<FORM_ID>/leads

The response:

{
  "data": [
    {
      "created_time": "2018-02-28T08:49:14+0000", 
      "id": "<LEAD_ID>", 
      "ad_id": "<AD_ID>",
      "form_id": "<FORM_ID>",
      "field_data": [
        {
          "name": "car_make",
          "values": [
            "Honda"
          ]
        }, 
        {
          "name": "full_name", 
          "values": [
            "Joe Example"
          ]
        }, 
        {
          "name": "email", 
          "values": [
            "joe@example.com"
          ]
        },
      ]
    }
  ],
  "paging": {
    "cursors": {
      "before": "OTc2Nz3M8MTgyMzU1NDMy", 
      "after": "OTcxNjcyOTg8ANTI4NzE4"
    }
  }
}

Filtering Leads

This example filters leads based on timestamps. Timestamps should be Unix timestamp.

curl -G \
  -d "filtering=[{'field':'time_created','operator':'GREATER_THAN','value':<TIMESTAMP>}]" \
  -d "access_token=<ACCESS_TOKEN>" \
  https://graph.facebook.com/<API_VERSION>/<AD_ID>/leads

Tokenization

If the form has customized field IDs, the fields and values returned will be the specified fields and values.

Webhooks

Get real time updates when leads are filled out. See Lead Ads with Webhooks, Video.

Many CRMs provide real time updates to migrate Lead ads data into the CRMs. See Available CRM Integration.

Setup

1. Set up endpoint to handle realtime pings

The ping for real time updates is structured as follows. Read more at Real Time Updates, Blog.

Multiple changes can come in through the ping in the changes array.

array(
  "object" => "page",
  "entry" => array(
    "0" => array(
      "id" => 153125381133,
      "time" => 1438292065,
      "changes" => array(
        "0" => array(
          "field" => "leadgen",
          "value" => array(
            "leadgen_id" => 123123123123,
            "page_id" => 123123123,
            "form_id" => 12312312312,
            "adgroup_id" => 12312312312,
            "ad_id" => 12312312312,
            "created_time" => 1440120384
          )
        ),
        "1" => array(
          "field" => "leadgen",
          "value" => array(
            "leadgen_id" => 123123123124,
            "page_id" => 123123123,
            "form_id" => 12312312312,
            "adgroup_id" => 12312312312,
            "ad_id" => 12312312312,
            "created_time" => 1440120384
          )
        )
      )
    )
  )
)

You can use leadgen_id to retrieve data associated with the lead:

use FacebookAds\Object\Lead;

$form = new Lead(<LEAD_ID>);
$form->read();
from facebookads.adobjects.lead import Lead

lead = Lead(<LEAD_ID>)
lead.remote_read()
curl -G \
  -d 'access_token=<ACCESS_TOKEN>' \
  https://graph.facebook.com/v2.8/<LEAD_ID>

The response:

{
  "created_time": "2015-02-28T08:49:14+0000", 
  "id": "<LEAD_ID>", 
  "ad_id": "<AD_ID>",
  "form_id": "<FORM_ID>",
  "field_data": [{
    "name": "car_make",
    "values": [
      "Honda"
    ]
  }, 
  {
    "name": "full_name", 
    "values": [
      "Joe Example"
    ]
  }, 
  {
    "name": "email", 
    "values": [
      "joe@example.com"
    ]
  }]
}

2. Subscribe app to leadgen events

To subcribe to leadgen event, your server should respond with HTTP GET requests as described in Receiving API Updates in Real Time with Webhooks.

After your callback URL is set up, subscribe to the leadgen webhook in your Apps's Dashboard or through an API call:

To subscribe through the API you need an app access token, not a user access token:

curl \
-F "object=page" \
-F "callback_url=https://www.yourcallbackurl.com" \
-F "fields=leadgen" \
-F "verify_token=abc123" \
-F "access_token=<APP_ACCESS_TOKEN>" \
"https://graph.facebook.com/<API_VERSION>/<APP_ID>/subscriptions"

3. Get page token

Generate a single, long-lived page token to continuously fetch data without worrying about it expiration:

  1. Get a regular user token.
  2. Convert it into a long-lived token
  3. with the long lived user's access token, request the page token:
use FacebookAds\Api;

$accounts = Api::instance()->call('/me/accounts');
from facebookads import FacebookAdsApi

accounts = FacebookAdsApi.get_default_api().call('GET', ('/me/accounts',))
new User("me", context).getAccounts()
  .execute();
curl -G \
  -d 'access_token=<ACCESS_TOKEN>' \
  https://graph.facebook.com/v2.8/me/accounts

The response:

{
  "data": [
    {
      "access_token": "[REDACTED]",
      "category": "Pet",
      "name": "Puppy",
      "id": "153125381133",
      "perms": [
        "ADMINISTER",
        "EDIT_PROFILE",
        "CREATE_CONTENT",
        "MODERATE_CONTENT",
        "CREATE_ADS",
        "BASIC_ADMIN"
      ]
    },
  ]
}

This long-lived page token has no expiration date and you can hard-code it in simple RTU integrations to get leads data.

4. Subscribe the page to the app

With the access_token for the page you need to subscribe, make the call below to authenticate an app for your page. You need to have at least MODERATE_CONTENT permission to the page in order to perform this action.

curl \
-F "access_token=<PAGE_ACCESS_TOKEN>" \
"https://graph.facebook.com/<API_VERSION>/<PAGE_ID>/subscribed_apps"

The Facebook app associated with your token is authenticated for page updates; you do not need to specify app ID.

On success, real time pings occur on events with a delay of up to a few minutes. See Troubleshooting Real-time Integrations.

Continued Flow

This feature is available by whitelist only.

With this optional feature, people complete the final step for a Lead ad on the advertiser's website. The lead ad collects all data provided and passes it to a destination URL with a hash or POST request. Use this approach only if you need data that Facebook is unwilling to collect, such as passwords for creating an account.

Optimization goals for Continued Flow Lead Ads

If you are use continued flow with Lead Ads, in addition to LINK_CLICKS and LEAD_GENERATION, you can also use PIXEL_OPTIMIZATION as the optimization goal. You can optimize your lead ads with a downstream Facebook pixel event. This is only available for continued flow.

Without Signed Requests

Facebook will make the following GET request:

GET https://#form_name=Form%201&full_name=Joe%20Example&email=joe%40example.com (https://%3Cdestination_url%3E/#form_name=Form%201&full_name=Joe%20Example&email=joe%40example.com)

Your destination URL should read this request with JavaScript. You may request we make a POST request. The POST body looks like this:

{
  "issued_at": 131456789,
  "form_name": "Form 1",
  "full_name": "Joe Example",
  "email": "joe@example.com"
}

Signed Requests

Signed requests offer more security as you can verify requests actually come from Facebook.

The payload provided by Facebook in signed requests are base64 encoded and contain a signature using your app_secret. This means you must have a Facebook app to use signed requests.

Facebook otherwise makes requests in the same way as non-signed requests.

GET https://<DESTINATION_URL>#signedrequest=<SIGNED_REQUEST_TOKEN>

If you choose to use a POST request, the POST body looks like this:

{
  "algorithm": "HMAC-SHA256",
  "issued_at": 131456789,
  "signedrequest": "<SIGNED_REQUEST_TOKEN>",
}

Once you get a signed request, perform three steps:

  1. Split the signed request into two parts delineated by a '.' for example 238fsdfsd.oijdoifjsidf899)
  2. Decode the first part, the encoded signature, from base64url
  3. Decode the second part, the 'payload', from base64url and then decode the resulting JSON object

You can do this in any modern programming language, such as PHP:

function parse_signed_request($signed_request) {
  list($encoded_sig, $payload) = explode('.', $signed_request, 2); 

  $secret = "<APP_SECRET>"; // Use your app secret here

  // decode the data
  $sig = base64_url_decode($encoded_sig);
  $data = json_decode(base64_url_decode($payload), true);

  // confirm the signature
  $expected_sig = hash_hmac('sha256', $payload, $secret, $raw = true);
  if ($sig !== $expected_sig) {
    error_log('Bad Signed JSON signature!');
    return null;
  }

  return $data;
}

function base64_url_decode($input) {
  return base64_decode(strtr($input, '-_', '+/'));
}

The response from parse_signed_request() returns the query data collected from the lead ad.

Test Continued Flow

To test your integration, use Continued Flow Test Tool. The app sends data to your destination URL the same way continued flow does.

Default Keys

The following are keys for default fields we send in the form:

FieldKey

Email

email

Full name

full_name

Date of birth (mm/dd/yyyy)

date_of_birth

State

state

Zip

zip_code

Phone number

phone_number

Province

province

Post code

post_code

Street address

street_address

City

city

First Name

first_name

Last Name

last_name

Gender

gender

Marital Status

marital_status

Relationship status

relationship_status

Military status

military_status

Company name

company_name

Work phone number

work_phone_number

Job title

job_title

Work email

work_email