Store Visits Objective

The Store Visits objective helps marketers achieve their goal of driving customers into brick and mortar stores. Access to the Ads and Insight API for Store Visits is provided on a limited basis only. Please contact your Facebook Representative.

Placements

Currently available for Facebook Desktop or Mobile.

Delivery Optimization

There are two optimization options available for the Store Visits objective:

  • Reach Optimization - Available to all advertisers. It optimizes for daily unique reach and shows Impressions as the default metric in Ads Manager reporting.

  • Store Visits Optimization - Available to eligible advertisers. It optimizes for the lowest cost per attributed store visit by increasing delivery towards customers who are most likely to visit a store. Store visits is the default metric within Ads Manager reporting when available.

Create Ads

To create Dynamic Ads for this objective, your page must use Facebook Locations.

Requirements:

  • Campaigns must have objective set to STORE_VISITS.
  • Campaigns must have promoted_object set to the corresponding <PARENT_PAGE_ID>.
  • Ad set promoted_object and targeting must contain a place_page_set_id of a <PAGE_SET_ID>
  • Ad set optimization_goal must be set to STORE_VISITS or REACH
  • Ad set billing_event should be IMPRESSIONS

To create Dynamic Ads with this objective:

  1. Create a PageSet
  2. Create a Campaign
  3. Create an Ad Set
  4. Create an Ad Creative
  5. Create an Ad

Step 1. Create a PageSet

Facebook ultimately uses PageSet to target ads and as the promoted object for your ad. To create a PageSet:

  • Collect child locations, which are independent store pages and locations for your primary business. Your primary business is known as a parent or parent page
  • Create a Locations JSON structure to create the PageSet.
  • Create the PageSet with the Locations JSON

Collect all Child Locations

To create a PageSet, first output all the locations from a Page. <PARENT_PAGE_ID> is the ID of the Parent page of your parent-child locations. This call retrieves all child pages and locations for a parent page and returns the longitude and latitude for each location.

curl -X GET \ -d 'fields="location{latitude,longitude},is_permanently_closed"' \ -d 'limit=30000' \ -d 'access_token=<ACCESS_TOKEN>' \ https://graph.facebook.com/v3.1/{page-id}/locations
const adsSdk = require('facebook-nodejs-ads-sdk'); const Page = adsSdk.Page; let access_token = '<ACCESS_TOKEN>'; let app_secret = '<APP_SECRET>'; let app_id = '<APP_ID>'; let id = '<ID>'; const api = adsSdk.FacebookAdsApi.init(access_token); const showDebugingInfo = true; // Setting this to true shows more debugging info. if (showDebugingInfo) { api.setDebug(true); } const logApiCallResult = (apiCallName, data) => { console.log(apiCallName); if (showDebugingInfo) { console.log('Data:' + JSON.stringify(data)); } }; let fields, params; fields = [ 'location{latitude', 'longitude}', 'is_permanently_closed', ]; params = { 'limit' : '30000', }; let locationss = (new Page(id)).getLocations( fields, params ); logApiCallResult('locationss api call complete.', locationss);
require __DIR__ . '/vendor/autoload.php'; use FacebookAds\Object\Page; use FacebookAds\Api; use FacebookAds\Logger\CurlLogger; $access_token = '<ACCESS_TOKEN>'; $app_secret = '<APP_SECRET>'; $app_id = '<APP_ID>'; $id = '<ID>'; $api = Api::init($app_id, $app_secret, $access_token); $api->setLogger(new CurlLogger()); $fields = array( 'location{latitude', 'longitude}', 'is_permanently_closed', ); $params = array( 'limit' => '30000', ); echo json_encode((new Page($id))->getLocations( $fields, $params )->getResponse()->getContent(), JSON_PRETTY_PRINT);
from facebookads.adobjects.page import Page from facebookads.api import FacebookAdsApi access_token = '<ACCESS_TOKEN>' app_secret = '<APP_SECRET>' app_id = '<APP_ID>' id = '<ID>' FacebookAdsApi.init(access_token=access_token) fields = [ 'location{latitude', 'longitude}', 'is_permanently_closed', ] params = { 'limit': '30000', } print Page(id).get_locations( fields=fields, params=params, )
import com.facebook.ads.sdk.*; import java.io.File; import java.util.Arrays; public class SAMPLE_CODE_EXAMPLE { public static void main (String args[]) throws APIException { String access_token = \"<ACCESS_TOKEN>\"; String app_secret = \"<APP_SECRET>\"; String app_id = \"<APP_ID>\"; String id = \"<ID>\"; APIContext context = new APIContext(access_token).enableDebug(true); new Page(id, context).getLocations() .setParam(\"limit\", \"30000\") .requestField(\"location{latitude\") .requestField(\"longitude}\") .requestIsPermanentlyClosedField() .execute(); } }
require 'facebook_ads' access_token = '<ACCESS_TOKEN>' app_secret = '<APP_SECRET>' app_id = '<APP_ID>' id = '<ID>' FacebookAds.configure do |config| config.access_token = access_token config.app_secret = app_secret end page = FacebookAds::Page.get(id) locationss = page.locations({ fields: { 'location{latitude''longitude}''is_permanently_closed' }, limit: '30000', })

Sample output:

{
  "data": [
    {
      "location": {
        "latitude": 29.173384,
        "longitude": 48.098807
      },
      "is_permanently_closed": false,
      "id": "1788030244802584"
    },
    {
      "location": {
        "latitude": 29.303635,
        "longitude": 47.937725
      },
      "is_permanently_closed": false,
      "id": "261533444245300"
    },
    {
      "location": {
        "latitude": 29.302303,
        "longitude": 47.933178
      },
      "is_permanently_closed": false,
      "id": "179435399132774"
    },
    {
      "location": {
        "latitude": 29.302591,
        "longitude": 47.931801
      },
      "is_permanently_closed": false,
      "id": "1790317704582144"
    }
  ],
  "paging": {
    "cursors": {
      "before": "MTc4ODAzMDI0NDgwMjU4NAZDZD",
      "after": "MTA4MTU4NjU5NjA5MDA4"
    }
  }
}

Create a Locations JSON Structure

Iterate through each entry in the returned results and ensure each location is open for business by checking the is_permanently_closed field. Get the estimated radius with two GET Graph API calls. Alternatively, you can make a batched API call to generate the values as we show below.

Individual Requests

This call is made against using each child page's latitude and longitude from the JSON results. This returns the estimated radius for each location.

curl -X GET \ -d 'type="adradiussuggestion"' \ -d 'latitude=51.5152253' \ -d 'longitude=-0.1423029' \ -d 'access_token=<ACCESS_TOKEN>' \ https://graph.facebook.com/v3.1/search/
const adsSdk = require('facebook-nodejs-ads-sdk'); const Page = adsSdk.Page; let access_token = '<ACCESS_TOKEN>'; let app_secret = '<APP_SECRET>'; let app_id = '<APP_ID>'; let id = '<ID>'; const api = adsSdk.FacebookAdsApi.init(access_token); const showDebugingInfo = true; // Setting this to true shows more debugging info. if (showDebugingInfo) { api.setDebug(true); } const logApiCallResult = (apiCallName, data) => { console.log(apiCallName); if (showDebugingInfo) { console.log('Data:' + JSON.stringify(data)); } }; let fields, params; fields = [ ]; params = { 'type' : 'adradiussuggestion', 'latitude' : '51.5152253', 'longitude' : '-0.1423029', }; let sample_code = (new Page(id)).get( fields, params ); logApiCallResult('sample_code api call complete.', sample_code);
require __DIR__ . '/vendor/autoload.php'; use FacebookAds\Object\Page; use FacebookAds\Api; use FacebookAds\Logger\CurlLogger; $access_token = '<ACCESS_TOKEN>'; $app_secret = '<APP_SECRET>'; $app_id = '<APP_ID>'; $id = '<ID>'; $api = Api::init($app_id, $app_secret, $access_token); $api->setLogger(new CurlLogger()); $fields = array( ); $params = array( 'type' => 'adradiussuggestion', 'latitude' => '51.5152253', 'longitude' => '-0.1423029', ); echo json_encode((new Page($id))->getSelf( $fields, $params )->getResponse()->getContent(), JSON_PRETTY_PRINT);
from facebookads.adobjects.page import Page from facebookads.api import FacebookAdsApi access_token = '<ACCESS_TOKEN>' app_secret = '<APP_SECRET>' app_id = '<APP_ID>' id = '<ID>' FacebookAdsApi.init(access_token=access_token) fields = [ ] params = { 'type': 'adradiussuggestion', 'latitude': '51.5152253', 'longitude': '-0.1423029', } print Page(id).get( fields=fields, params=params, )
import com.facebook.ads.sdk.*; import java.io.File; import java.util.Arrays; public class SAMPLE_CODE_EXAMPLE { public static void main (String args[]) throws APIException { String access_token = \"<ACCESS_TOKEN>\"; String app_secret = \"<APP_SECRET>\"; String app_id = \"<APP_ID>\"; String id = \"<ID>\"; APIContext context = new APIContext(access_token).enableDebug(true); new Page(id, context).get() .setParam(\"type\", \"adradiussuggestion\") .setParam(\"latitude\", \"51.5152253\") .setParam(\"longitude\", \"-0.1423029\") .execute(); } }
require 'facebook_ads' access_token = '<ACCESS_TOKEN>' app_secret = '<APP_SECRET>' app_id = '<APP_ID>' id = '<ID>' FacebookAds.configure do |config| config.access_token = access_token config.app_secret = app_secret end page = FacebookAds::Page.get(id ,'')

Batch Requests

You can also batch multiple requests into a single request:

curl \
  -F "access_token=<ACCESS_TOKEN>" \
  -F "include_headers=false" \
  -F "batch=[
    {
      \"method\": \"GET\",
      \"relative_url\": \"/VERSION/search?type=adradiussuggestion&amp;latitude=29.173384&amp;longitude=48.098807\"
    },
    {
      \"method\": \"GET\",
      \"relative_url\": \"/VERSION/search?type=adradiussuggestion&amp;latitude=29.303635&amp;longitude=47.937725\"
    }
  ]" \
  "https://graph.facebook.com"

Final Locations JSON

Use the radius and distance_unit in results from the previous calls and the <CHILD_LOCATION_ID> as the page_id, create this JSON; this is the final locations JSON:

[
    {
      "page_id": 1788030244802584,
      "radius": 1,
      "distance_unit": "mile"
    },
    {
      "page_id": 261533444245300,
      "radius": 1,
      "distance_unit": "mile"
    }
]

Create Pageset with Locations JSON

The maximum number of locations in a Page Set is 10000. Make this call to make a PageSet.

curl -X POST \
 -d "name=My Grand Pageset" \
 -d "parent_page=<PARENT_PAGE_ID>" \
 -d "pages=<LOCATIONS_JSON_STRUCTURE>" \
 -d "access_token=<ACCESS_TOKEN>" \
 "https://graph.facebook.com/VERSION/act_<AD_ACCOUNT_ID>/ad_place_page_sets/"

This returns a PageSet ID which you use later. The result and <PAGE_SET_ID> looks like this:

{
  "id": <PAGE_SET_ID>
}

If the number of pages is too large for a cURL call you can create a text file with LOCATIONS_JSON_STRUCTURE and pass it in to the pages attribute with this flag: -F "pages=&lt;locations_json_structure.txt"

Step 2: Create A Campaign

Create a campaign with objective set to STORE_VISITS and your parent page ID as promoted object.

curl -X POST \
 -d "name=Store Visits Campaign" \
 -d "objective=STORE_VISITS" \
 -d "promoted_object={'page_id':'<PARENT_PAGE_ID>'}" \
 -d "status=PAUSED" \
 -d "access_token=<ACCESS_TOKEN>" \
 "https://graph.facebook.com/VERSION/act_<AD_ACCOUNT_ID>/campaigns"

See Ad Campaign, Reference.

Step 3: Create An Ad Set

Create an ad set to contain your ad. See Reference, Ad Set, Reference, Targeting specs and Reference, Page locations.

At delivery time, Facebook invalidates any ads targeting locations more than 50 miles away from the nearest page location, known as local page.

curl \
  -F 'name=Store Visits Ad Set' \
  -F 'promoted_object={"place_page_set_id":"<PAGE_SET_ID>"}' \
  -F 'optimization_goal=STORE_VISITS' \
  -F 'billing_event=IMPRESSIONS' \
  -F 'is_autobid=true' \
  -F 'daily_budget=1000' \
  -F 'campaign_id=<CAMPAIGN_ID>' \
  -F "targeting={
    'place_page_set_ids': ['<PAGE_SET_ID>'],
    'device_platforms': ['mobile','desktop'],
    'facebook_positions': ['feed']
  }" \
  -F 'access_token=<ACCESS_TOKEN>' \
  https://graph.facebook.com/VERSION/act_<AD_ACCOUNT_ID>/adsets

Geographical Targeting

You can also target by geo_locations in store visits campaigns. Note you can only either use geo_locations or place_page_set_ids in ad set targeting for this objective.

We support all types of geo_location targeting in Advanced Targeting and Placement, including targeting by countries, cities, and zip codes. You can also select location_types such as recent, home, or travel_in.

You should still provide place_page_set_id in the promoted_object. This page set has to be a page set without any explicit set of locations. See Creating the Pageset with the Locations JSON to create this page set. However in this case, do not pass the parameter pages.

First create a page set you later provide in a promoted object:

curl -X POST \
 -d "name=My geo targeting page set" \
 -d "parent_page=PARENT_PAGE_ID" \
 -d "access_token=ACCESS_TOKEN" \
 "https://graph.facebook.com/VERESION/act_AD_ACCOUNT_ID/ad_place_page_sets/"

Note you do not need to provide the parameter pages as you normally do. Now you can create and ad set with the store visits objective which targets on geo_locations:

curl \
  -F 'name=Store Visits Ad Set' \
  -F 'promoted_object={"place_page_set_id":"<PAGE_SET_ID>"}' \
  -F 'optimization_goal=STORE_VISITS' \
  -F 'billing_event=IMPRESSIONS' \
  -F 'is_autobid=true' \
  -F 'daily_budget=1000' \
  -F 'campaign_id=CAMPAIGN_ID' \
  -F "targeting={
    'geo_locations': {"countries":["US"],"location_types": ["home"]}, 
    'device_platforms': ['mobile','desktop'],
    'facebook_positions': ['feed']
  }" \
  -F 'access_token=ACCESS_TOKEN' \
  https://graph.facebook.com/VERSION/act_AD_ACCOUNT_ID/adsets

We automatically deliver ads for the store which is closest to the person viewing your ad.

Step 4: Provide An Ad Creative

Creative Template You can dynamically insert creative based on someone's location. Customize your creative using a set of template placeholders, and Facebook replaces the placeholders in your ads at runtime with:

  • Data from the nearest page location, if dynamic_ad_voice is set to DYNAMIC.
  • Data from your main page, if dynamic_ad_voice is set to STORY_OWNER.

Available placeholders:

  • {{page.location.city}}
  • {{page.location.region}}
  • {{page.location.street_address}}
  • {{page.name}}
  • {{page.phone_number}}

Call To Actions

You can also dynamically ad call-to-action buttons (CTAs) based on someone's location:

  • When using GET_DIRECTIONS or CALL_NOW, the CTA value field is not required. Users will automatically be directed to nearest location or prompted to call the nearest location phone number.
  • MESSAGE_PAGE is allowed only if dynamic_ad_voice is set to STORY_OWNER. Messages will be delivered to the main page.
  • Optional field. If not specified for individual ads, we display a Like Page button.

For details, see Reference, Ad creative

dynamic_ad_voice type in call_to_action

DYNAMIC

CALL_NOW


GET_DIRECTIONS

STORY_OWNER

CALL_NOW


GET_DIRECTIONS


LEARN_MORE


MESSAGE_PAGE

Examples

Provide an ad creative using dynamic page name and city:

curl \
  -F 'dynamic_ad_voice=DYNAMIC' \
  -F 'object_story_spec={ 
    "page_id": "<PARENT_PAGE_ID>", 
    "template_data": { 
      "description": "Ad Description", 
      "link": "<URL>", 
      "message": "Ad Message for {{page.location.city}}", 
      "name": "{{page.name}}", 
      "picture": "<IMAGE_URL>" 
    } 
  }' \
  -F 'access_token=<ACCESS_TOKEN>' \
  https://graph.facebook.com/VERSION/act_<AD_ACCOUNT_ID>/adcreatives

Ad Creative with Map Card

To use a map card add a place_data structure in a attachment in the child_attachments field for your Ad Creative.

In this example, the map, with a Facebook store locator link is the second item in the child_attachments array. You must provide have at least one item in addition to the map card:

curl \
  -F 'dynamic_ad_voice=DYNAMIC' \
  -F 'object_story_spec={ 
    "page_id": "<PARENT_PAGE_ID>", 
    "template_data": { 
      "description": "Ad Description", 
      "link": "<URL>", 
      "message": "Ad Message for {{page.location.city}}", 
      "name": "{{page.name}}", 
      "child_attachments":[
        {
          "description": "Come visit us!",
          "link": "http://yourweburl.com",
          "name": "{{page.location.street_address}} - {{page.location.city}}",
          "call_to_action": {
            "type":"GET_DIRECTIONS"
          },
        },
        {
          "link": "https://fb.com/store_locator",
          "name": "Check out our stores.",
          "place_data": {
            "type":"DYNAMIC"
          },
        }
      ]
    } 
  }' \
  -F 'access_token=<ACCESS_TOKEN>' \
  https://graph.facebook.com/VERSION/act_<AD_ACCOUNT_ID>/adcreatives

Store Locator, Link Destination

When you create an Ad, if you set the link to 'https://fb.com/store_locator' the ad appears with the Store Locator as the link destination.

Step 5: Create An Ad

Create an ad as usual:

curl \
  -F 'name=My Ad' \
  -F 'adset_id=<AD_SET_ID>' \
  -F 'creative={"creative_id":"<CREATIVE_ID>"}' \
  -F 'status=PAUSED' \
  -F 'access_token=<ACCESS_TOKEN>' \
  https://graph.facebook.com/VERSION/act_<AD_ACCOUNT_ID>/ads

To create ads for Store Visits, your Page and Ad Account must be approved for Store Visit Measurement. Otherwise, an error similar to Reach estimate isn't available because 'store_visit' isn't a valid action type appears.

Store Visits Measurement

Store visits is an estimated metric based on data from users with location services enabled. It ultimately offers store visit measurement and optimization for the Store Visits objective. Store visit measurement is only available for campaigns with the Store Visits objective.

To access the Store Visits metric in Ads Manager or Marketing API you must be whitelisted. Contact your Facebook Representative for access.

Store visits are based on clicks and views to ads using the store visits objective. It's an estimated number of visits to an advertiser's business locations by people who have seen or clicked on each store's ads. You can configure the attribution window; you can choose to customize it based on 1-, 7- or 28-day clicks or views. The ad account's default attribution applies unless you customize the configuration. See Insights API, Attribution Window.

These sections describe features added to:

The features relate to reporting for the following items:

  • Store Visits: The number of estimated visits to your business locations as a result of your ads
  • Cost per Store Visit: The average cost for each estimated visit to your business locations as a result of your ads

With Ads Manager

See columns under ENGAGEMENT: ACTIONS which appear in the reporting interface for Store Visits and the Cost per Store Visit.

Store visit data is only available for business locations that Facebook team confirms as measurable for a campaign.

With Insights API

You can get data on store visits from the Insights API. We provide two additional fields in the general insights calls: cost_per_store_visit_actions and store_visit_actions. See Insights, Reference. For example:

curl -X GET \ -d 'fields="cost_per_store_visit_action,store_visit_actions"' \ -d 'access_token=<ACCESS_TOKEN>' \ https://graph.facebook.com/v3.1/<AD_SET_ID>/insights
const adsSdk = require('facebook-nodejs-ads-sdk'); const AdSet = adsSdk.AdSet; const AdsInsights = adsSdk.AdsInsights; let access_token = '<ACCESS_TOKEN>'; let app_secret = '<APP_SECRET>'; let app_id = '<APP_ID>'; let id = '<ID>'; const api = adsSdk.FacebookAdsApi.init(access_token); const showDebugingInfo = true; // Setting this to true shows more debugging info. if (showDebugingInfo) { api.setDebug(true); } const logApiCallResult = (apiCallName, data) => { console.log(apiCallName); if (showDebugingInfo) { console.log('Data:' + JSON.stringify(data)); } }; let fields, params; fields = [ 'cost_per_store_visit_action', 'store_visit_actions', ]; params = { }; let insightss = (new AdSet(id)).getInsights( fields, params ); logApiCallResult('insightss api call complete.', insightss);
require __DIR__ . '/vendor/autoload.php'; use FacebookAds\Object\AdSet; use FacebookAds\Object\AdsInsights; use FacebookAds\Api; use FacebookAds\Logger\CurlLogger; $access_token = '<ACCESS_TOKEN>'; $app_secret = '<APP_SECRET>'; $app_id = '<APP_ID>'; $id = '<ID>'; $api = Api::init($app_id, $app_secret, $access_token); $api->setLogger(new CurlLogger()); $fields = array( 'cost_per_store_visit_action', 'store_visit_actions', ); $params = array( ); echo json_encode((new AdSet($id))->getInsights( $fields, $params )->getResponse()->getContent(), JSON_PRETTY_PRINT);
from facebookads.adobjects.adset import AdSet from facebookads.adobjects.adsinsights import AdsInsights from facebookads.api import FacebookAdsApi access_token = '<ACCESS_TOKEN>' app_secret = '<APP_SECRET>' app_id = '<APP_ID>' id = '<ID>' FacebookAdsApi.init(access_token=access_token) fields = [ 'cost_per_store_visit_action', 'store_visit_actions', ] params = { } print AdSet(id).get_insights( fields=fields, params=params, )
import com.facebook.ads.sdk.*; import java.io.File; import java.util.Arrays; public class SAMPLE_CODE_EXAMPLE { public static void main (String args[]) throws APIException { String access_token = \"<ACCESS_TOKEN>\"; String app_secret = \"<APP_SECRET>\"; String app_id = \"<APP_ID>\"; String id = \"<ID>\"; APIContext context = new APIContext(access_token).enableDebug(true); new AdSet(id, context).getInsights() .requestField(\"cost_per_store_visit_action\") .requestField(\"store_visit_actions\") .execute(); } }
require 'facebook_ads' access_token = '<ACCESS_TOKEN>' app_secret = '<APP_SECRET>' app_id = '<APP_ID>' id = '<ID>' FacebookAds.configure do |config| config.access_token = access_token config.app_secret = app_secret end ad_set = FacebookAds::AdSet.get(id) insightss = ad_set.insights({ fields: { 'cost_per_store_visit_action''store_visit_actions' }, })

Sample output looks like:

{
  "data": [
    {
      "cost_per_store_visit_actions": [
        {
          "action_type": "store_visit",
          "value": "0.193995",
        }
      ],
      "store_visit_actions": [
        {
          "action_type": "store_visit",
          "value": "93625",
        }
      ],
      "date_start": "2017-12-01",
      "date_stop": "2017-12-30"
    }
  ],
  "paging": {
    "cursors": {
      "before": "AAAAAA",
      "after": "AAAAAA"
    }
  },
  "__fb_trace_id__": "aaaaaaaaaaa"
}   

This result shows the cost per store visit and the estimated value of the store visits. See

FieldDescription
point_estimate
int32

The point prediction of the value