How To Build Your Own Facebook Ads Custom Audience From Scratch Programmatically (Meta Update 2024)

Facebook/Meta Ads are a crucial channel for many businesses. However, success hinges on targeting the right audience.

Traditional methods can be hit-or-miss, leading to wasted ad spend and poor results.

Even if you manage to create a winning Facebook ad, it's usually a result from months of testing and sending Meta more data to improve your ad targeting.

Why not opt to skip that?

That said, in this guide we'll be focusing on programmatically creating a Meta custom audience from scratch, offering a solution for businesses, especially in the B2B sector, to precisely target their ideal customer profile (ICP) immediately on Meta ads (regardless of industry), rather than spending weeks or even months optimizing your ads just to barely break even.

It doesn't matter the level of sophistication your target audience is in. Chances are they probably have a Facebook, Instagram or WhatsApp.

You just need to find them.

The problem with generic Meta ads targeting

The primary challenge lies in Meta's broad targeting mechanisms, which may not be able to sufficiently find your target audience (at a cost that makes sense, anyway).

After all: Meta makes more money when you spend more on advertising. They don't exactly have an incentive to give you the best performance at the lowest cost.

Generally, the most effective ads rely on conversion events. These ads require lots of data over a long period-of-time to truly optimize themselves.

These ads all revolve around using a "pixel" which sends Meta more data about what type of people are responding to your ad.

You can't exactly skip Meta's process of optimizing your ads delivery over time, but there is a bit of a shortcut for fixing Meta's original "lack of data problem" that leads to often originally unsatisfactory Facebook ads performance.

The shortcut to better Meta ad performance?

You take control by generating and importing your own custom audience into Meta ads.

This approach puts you in the driver's seat, ensuring your audience is precisely who you want them to be.

And the best part is you don't even need an existing customer audience. We can programmatically generate it with specific identifying characteristics create an enriched list of prospects that exactly fit your ICP (ideal customer profile).

How?

Well, you're on the brink of discovering just that.

But before we dive into the “how,” let's establish a clear understanding of what custom audiences really are:

What is a custom audience?

A custom audience is one of two different things:

  1. your customer database, such as website traffic, email list, or beyond

  2. a detailed prospecting list generated from a B2B data provider or enrichment tool (like us)

Meta allows you to import these lists, and then you can either advertise directly to the list of prospects contained on the list, or you can use it as a seed to generate wider audiences on Meta very similar to the list of prospects you imported.

So it seriously helps out in terms of putting your ad in front of people most likely to actually buy what you're selling. A big advantage.

The data-driven approach

Before you can create a custom audience, you need data. This is where a tool like our API comes into play.

You can use Proxycurl to programmatically generate you a custom audience that perfectly fits your ICP.

So rather than trusting a platform whose sole purpose is to increase your ad spend to generate a converting audience for you, particularly without a lot of data to go off of (if you haven’t run any ads already, or had a pixel on your site), you can guarantee that the seed of your custom audience fits your ICP.

Your ads will be placed in front of relevant people that actually want and need your product. No guessing by Meta required.

Is it really worth building your own custom audiences?

Admittedly, it takes a little more effort doing it this way. But it’s not really difficult.

You’ll need to have a bit of technical prowess, but you'll give yourself a massive advantage with a little bit more work.

That said: our API provides you several endpoints to be able to pull different data points/profiles in different ways. Meaning there are a few different ways to go about this.

The two main endpoints you would use for the purpose of building a Facebook/Meta custom audience, though, are likely going to be the Person Search Endpoint and the People Profile Endpoint.

Programmatically building your Meta custom audience

First things first, Meta allows you to use the following identifiers to build a custom audience:

Prospect identifiers that Meta supports

So, you could import a .CSV that looks like this for example:

Demo .CSV of what we can import into Meta

The more information you give Meta, the more likely they can accurately match the prospects contained in your Facebook custom audience .CSV.

With Proxycurl, we can search for prospects that fit within a given ICP and then extract the following identifiers:

  • Email
  • First name
  • Last name
  • Phone number
  • City, state, country

We'll use three different things to generate your custom audience:

  1. Python (though, you can obviously use any programming language of your choice)
  2. The Person Search Endpoint (to find prospects that fit within your ICP)
  3. The Person Profile Endpoint (to enrich said prospects)

Let’s say we’re a software company that provides some kind of product/service to financial service-based companies.

The following Python script automates the entire process of using the Proxycurl API to search for and enrich prospects that fit within the “financial services” based ICP:


import requests

import json

# Function to search for LinkedIn profiles

def search_linkedin_profiles(api_key):

    headers = {'Authorization': 'Bearer ' + api_key}

    api_endpoint = 'https://nubela.co/proxycurl/api/search/person/'

    params = {

        'country': 'US',

        'current_role_title': '(?i)founder',

        'industries': '(?i)financial services',

        'page_size': '100',

    }

    response = requests.get(api_endpoint, params=params, headers=headers)

    if response.status_code == 200:

        profiles_json = response.json()

        linkedin_urls = [profile['linkedin_profile_url'] for profile in profiles_json['results']]

        return linkedin_urls

    else:

        print(f"Failed to fetch LinkedIn profiles: Status code {response.status_code}")

        return []

# Function to enrich a single LinkedIn profile

def enrich_profile(api_key, linkedin_url):

    headers = {'Authorization': 'Bearer ' + api_key}

    api_endpoint = 'https://nubela.co/proxycurl/api/v2/linkedin'

    params = {

        'linkedin_profile_url': linkedin_url,

        'extra': 'include',

        # Add other parameters as needed

    }

    response = requests.get(api_endpoint, params=params, headers=headers)

    if response.status_code == 200:

        return response.json()

    else:

        print(f"Failed to enrich profile: Status code {response.status_code}")

        return None

# Main workflow

api_key = ‘Your_API_Key_Here'

linkedin_profiles = search_linkedin_profiles(api_key)

for url in linkedin_profiles:

    enriched_data = enrich_profile(api_key, url)

    if enriched_data:

        print(json.dumps(enriched_data, indent=4))

That returns us an enriched list of founders of financial services like as follows:

Result returned by Proxycurl's API, PyCharm interface

However, we can still do better than this.

We can automate the entire process so that it exports it to a Meta custom audience import ready .CSV by slightly modifying this script.

Here’s what the new script looks like:


import requests

import csv

# Function to search for LinkedIn profiles

def search_linkedin_profiles(api_key):

    headers = {'Authorization': 'Bearer ' + api_key}

    api_endpoint = 'https://nubela.co/proxycurl/api/search/person/'

    params = {

        'country': 'US',

        'current_role_title': '(?i)founder',

        'industries': '(?i)financial services',

        'page_size': '100',

    }

    response = requests.get(api_endpoint, params=params, headers=headers)

    if response.status_code == 200:

        profiles_json = response.json()

        linkedin_urls = [profile['linkedin_profile_url'] for profile in profiles_json['results']]

        return linkedin_urls

    else:

        print(f"Failed to fetch LinkedIn profiles: Status code {response.status_code}")

        return []

# Function to enrich a single LinkedIn profile

def enrich_profile(api_key, linkedin_url):

    headers = {'Authorization': 'Bearer ' + api_key}

    api_endpoint = 'https://nubela.co/proxycurl/api/v2/linkedin'

    params = {

        'linkedin_profile_url': linkedin_url,

    'personal_contact_number': 'include',

    'personal_email': 'include',

    }

    response = requests.get(api_endpoint, params=params, headers=headers)

    if response.status_code == 200:

        return response.json()

    else:

        print(f"Failed to enrich profile: Status code {response.status_code}")

        return None

# Function to write profile data to CSV

def write_profiles_to_csv(profiles, filename='linkedin_profiles.csv'):

    fieldnames = ['emails', 'phones', 'fn', 'ln', 'city', 'st', 'country']

    with open(filename, mode='w', newline='', encoding='utf-8') as file:

        writer = csv.DictWriter(file, fieldnames=fieldnames)

        writer.writeheader()

        for profile in profiles:

            writer.writerow({

                'emails': '; '.join(profile.get('personal_emails', [])),

                'phones': '; '.join(profile.get('personal_numbers', [])),

                'fn': profile.get('first_name', ''),

                'ln': profile.get('last_name', ''),

                'city': profile.get('city', ''),

                'st': profile.get('state', ''),

                'country': profile.get('country', '')

            })

# Main workflow

api_key = 'Your_API_Key_Here'  # Replace with your actual API key

linkedin_urls = search_linkedin_profiles(api_key)

# List to hold all enriched profiles

all_profiles = []

for url in linkedin_urls:

    profile_data = enrich_profile(api_key, url)

    if profile_data:

        structured_profile = {

            'personal_emails': profile_data.get('personal_emails', []),

            'personal_numbers': profile_data.get('personal_numbers', []),

            'first_name': profile_data.get('first_name', ''),

            'last_name': profile_data.get('last_name', ''),

            'city': profile_data.get('city', ''),

            'state': profile_data.get('state', ''),

            'country': profile_data.get('country', '')

        }

        all_profiles.append(structured_profile)

# Write the enriched profile data to a CSV file

write_profiles_to_csv(all_profiles)

So now when you click run, it does the same thing as before, but it exports it to a .CSV that includes all the information we need to import into Meta ads.

Exported prospect list into .CSV

Of course, you could edit this script in many ways as well, changing the targeted audience, and beyond.

Proxycurl can find enriched prospects for any brand. Especially B2B brands.

We work with companies in many different verticals, such as:

In the case of editing the above script, though, you’ll want to change the Person Search Endpoint part (view search parameters here) as that’s doing the targeting. The Person Profile Endpoint is just providing enrichment.

Now for the fun part:

Importing your new targeted custom audience into Meta ads

First, you’ll need to login to your Meta account and head over to the Ads Manager.

Then you’ll need to click the menu icon, and navigate to “Audiences”—next click “create audience”:

Creating custom audience 

You’ll see the same page as shown before:

Importing prospects on Meta

Click next, and upload the custom audience .CSV generated from the steps shown earlier:

Selecting and uploading your generated .CSV

Then you'll be asked to verify if everything is properly mapped. Make sure everything is being imported and no data point gets skipped:

Matching identifiers

Finally, click “import & create.”

Finalizing importing your new Facebook custom audience

You’re in business.

The best part has yet to come, though…

Creating lookalike audiences with your freshly imported Meta custom audience

Now that we’ve imported our custom audience, it’s incredibly easy to create a lookalike audience. A lookalike audience allows us to use our ultra targeted custom audience to find other extremely similar people.

Long gone is the need to feed a Facebook/Meta pixel months of data to get your ads in front of the right people.

Instead our newly imported custom audience will act as an enriched seed, giving you a jumpstart and feeding Meta the data they need to find people that'll convert for your brand.

All you’ll need to do to create your first lookalike audience is click “create a lookalike audience” as seen on the last step of the importation process.

You’ll then see the following options:

Creating a lookalike audience on Meta

Select your recently imported .CSV as the source, and then you can customize to your liking. The smaller the lookalike audience, the more accurate the audience will be.

Generally, what you’ll find is your ads will perform better to smaller lookalike audiences originally, but they’ll burn out relatively quickly too, at least quicker than a larger audience.

I suggest making a couple of different lookalike audiences and testing them with different angles and ads.

Using your new Meta audiences

To use your new audiences, go back to your dashboard and click “create campaign”:

Creating a new advertising campaign on Meta

Proceed to create your ad:

Continuing the ad creation process

Fill out your desired information and continue (this part isn’t relevant to the custom audience part).

You have two options here

Recently, Meta rolled out an “advantage+ audience” feature, which essentially works like a lookalike audience.

You can take the seed of the custom audience we imported here and tell Meta to find other people just like it:

Using Meta's "Advantage+" feature

Or you can also click to switch to “regular audience options,” and select the lookalike audience we made earlier:

Using Meta's previous lookalike audience feature

Truthfully, there’s no way to tell you which option will work better. I suggest testing both and looking at the results.

Both routes accomplish the same thing: you’re taking the highly targeted custom audience, and using it as a seed to start your Meta ads off right.

Actually, I lied. There’s a third option too:

If you’d like, you could use the same Python script above, adjust it to make more than 100 leads (such as 1,000 or 10,000), and then you could directly use that custom audience to serve ads on Meta ads.

You don’t need to do the lookalike or advantage+ audience part; it just helps expand the Meta custom audience seed you generate with Proxycurl.

That way, you entirely cut guesswork and the Meta algorithm out of the process of getting placed in front of an audience that you know has a very high chance of converting.

At that point, you’re just advertising to the accounts contained in the .CSV generated.

Here’s an edited script that generates a list of 1,000 prospects, for example:


import requests

import csv

# Function to search for LinkedIn profiles

def search_linkedin_profiles(api_key):

    headers = {'Authorization': 'Bearer ' + api_key}

    api_endpoint = 'https://nubela.co/proxycurl/api/search/person/'

    params = {

        'country': 'US',

        'current_role_title': '(?i)founder',

        'industries': '(?i)financial services',

        'page_size': '1000',

    }

    response = requests.get(api_endpoint, params=params, headers=headers)

    if response.status_code == 200:

        profiles_json = response.json()

        linkedin_urls = [profile['linkedin_profile_url'] for profile in profiles_json['results']]

        return linkedin_urls

    else:

        print(f"Failed to fetch LinkedIn profiles: Status code {response.status_code}")

        return []

# Function to enrich a single LinkedIn profile

def enrich_profile(api_key, linkedin_url):

    headers = {'Authorization': 'Bearer ' + api_key}

    api_endpoint = 'https://nubela.co/proxycurl/api/v2/linkedin'

    params = {

        'linkedin_profile_url': linkedin_url,

    'personal_contact_number': 'include',

    'personal_email': 'include',

    }

    response = requests.get(api_endpoint, params=params, headers=headers)

    if response.status_code == 200:

        return response.json()

    else:

        print(f"Failed to enrich profile: Status code {response.status_code}")

        return None

# Function to write profile data to CSV

def write_profiles_to_csv(profiles, filename='linkedin_profiles.csv'):

    fieldnames = ['emails', 'phones', 'fn', 'ln', 'city', 'st', 'country']

    with open(filename, mode='w', newline='', encoding='utf-8') as file:

        writer = csv.DictWriter(file, fieldnames=fieldnames)

        writer.writeheader()

        for profile in profiles:

            writer.writerow({

                'emails': '; '.join(profile.get('personal_emails', [])),

                'phones': '; '.join(profile.get('personal_numbers', [])),

                'fn': profile.get('first_name', ''),

                'ln': profile.get('last_name', ''),

                'city': profile.get('city', ''),

                'st': profile.get('state', ''),

                'country': profile.get('country', '')

            })

# Main workflow

api_key = 'Your_API_Key_Here'  # Replace with your actual API key

linkedin_urls = search_linkedin_profiles(api_key)

# List to hold all enriched profiles

all_profiles = []

for url in linkedin_urls:

    profile_data = enrich_profile(api_key, url)

    if profile_data:

        structured_profile = {

            'personal_emails': profile_data.get('personal_emails', []),

            'personal_numbers': profile_data.get('personal_numbers', []),

            'first_name': profile_data.get('first_name', ''),

            'last_name': profile_data.get('last_name', ''),

            'city': profile_data.get('city', ''),

            'state': profile_data.get('state', ''),

            'country': profile_data.get('country', '')

        }

        all_profiles.append(structured_profile)

# Write the enriched profile data to a CSV file

write_profiles_to_csv(all_profiles)

There are quite a few options, but regardless of the route you go, you seriously improve your chances of Meta ads showing you to the right people.

Again, you can edit the search parameters above according to your business. View all of the targeting options available here.

See, that wasn’t too hard, was it?

In fact, now you actually have the whole Python script you need to get this job done. You just need to customize it to your liking.

But you will need to create your Proxycurl account first before you do anything else.

That said: you can click here to create your Proxycurl account today for free.

How much does Proxycurl cost?

It's totally free to create a Proxycurl account, however, we use a credit based billing system. When you first sign up, you're issued 15 free credits to test out Proxycurl, with the possibility of getting more for completing certain tasks.

After that, you can either view our subscriptions here or opt for a pay-as-you-go-plan and just top up credits as needed. In that case, $10 would buy you 100 credits.

The price per endpoint does vary, so it's best to look at our documentation here to get an idea of the cost of credits it'll take (depending on how big of a custom audience you're planning to create, i.e. 100 prospects, or 1,000 prospects, enrichment parameters and so on).

Namely:

  1. Person Profile Endpoint (you can see all base credit costs and additional parameter costs)
  2. Person Search Endpoint (you can see all base credit costs and additional parameter costs)

Those are the two endpoints used in the script above and are likely the only two you would need outside of perhaps our relevant company related endpoints, which can also be seen on the documentation here.

The real question that should be asked here is:

How much money would showing your ads to the right audience on Meta ads bring in?

Seriously, ask yourself that.

If instead of wasting thousands of dollars on people who aren't interested in your product/service...

You could just get your brand in front of eyeballs that are not only actually qualified and interested, but have money.

How much money would that bring in?

That's the real question I think you should ask yourself.

Chances are, your audience is on Facebook, or Instagram, or the third-party websites that use Meta's advertising platform.

But of course, Meta doesn't really have much incentive to place you in front of them when they make money off of your continued ad spend.

And even on winning ads: typically, the Meta algorithm relies on building data over time as well as conversion events through their pixel. It can take weeks.

This process essentially force feeds Facebook ads (or Meta, whatever you prefer, same thing) accurate data that does in fact fit your ICP. And then, with that seed, Meta can find more ideal fits to put your ad in front of. Instantly giving you edge.

Did I tickle your fancy at all?

If so, now's the time to:

Create your Proxycurl account

It's at least worth giving Proxycurl a shot; there's minimal risk (pay-as-you-go), and a ton of upside.

Proxycurl will not only allow you to create rich Meta custom audiences that improve the targeting of your Meta ads, and work as a great seed to create an improved lookalike (or advantage+) audience--you can also do a lot more with it, such as:

Pretty much any B2B data related needs on people or companies you have, we can handle.

Simply stated, in 2024, you can't afford to not operate on accurate and fresh data. Your competitors are.

It's time to even the playing field and give yourself an advantage:

Here's your link to create your Proxycurl account today.

P.S. Have any questions? Don't hesitate to reach out to us at "hello@nubela.co".