Back to News for Developers

Introducing Dynamic Ads For Travel

August 2, 2016ByCecile Ho

We are pleased to announce that Dynamic Ads for travel is available to all users of the Marketing API, and is fully supported in PHP and Python SDKs.

With Dynamic Ads for travel you can automatically show ads to people on Facebook, Instagram and Audience Network who previously demonstrated interest in taking a trip on your website or mobile app. Whether someone has browsed hotels but didn't book or purchased a flight but no hotel, Dynamic Ads for travel let you target that person with relevant ads based on the specific dates, destination, and other trip details.

How to set up

  1. Set up your hotel catalog
  2. Build your audience based on their cross-device travel intent.
  3. Build a creative template that dynamically renders ads based on your catalog data and user intent.

Let's walk through the details for each of the steps above:

1. Set up Hotel Catalog

Currently, Dynamic Ads for travel supports hotel retargeting and cross-selling, which means you cross-sell people who searched or booked a flight with relevant hotels at their destination. You should have a catalog with your hotel properties and details such as hotel name, address, image, url, base_price, latitude and longitude. More details about the hotel catalog here.

Create Hotel Catalog

use FacebookAds\Object\ProductCatalog;
use FacebookAds\Object\Fields\ProductCatalogFields;
use FacebookAds\Object\Values\ProductCatalogVerticalValues;

$product_catalog = new ProductCatalog(null, <BUSINESS_ID>);

  ProductCatalogFields::NAME => "Hotel Catalog",
  ProductCatalogFields::VERTICAL => ProductCatalogVerticalValues::HOTELS,

from facebookads.adobjects.productcatalog import ProductCatalog

product_catalog = ProductCatalog(parent_id=<BUSINESS_ID>)

product_catalog[] = 'Hotel Catalog'
product_catalog[ProductCatalog.Field.vertical] = 'hotels'

curl \
  -F 'name=Hotel Catalog' \
  -F 'vertical=hotels' \
  -F 'access_token=<ACCESS_TOKEN>' \<BUSINESS_ID>/product_catalogs

Create Hotel Feed and Schedule Upload

You then set up hotel feed upload schedule or initiate the upload on demand to update the hotel information in the catalog. We support hotel feed in xml, tsv, csv formats. Please check out the samples here:

use FacebookAds\Object\ProductFeed;
use FacebookAds\Object\Fields\ProductFeedFields;
use FacebookAds\Object\Fields\ProductFeedScheduleFields;

$product_feed = new ProductFeed(null, <PRODUCT_CATALOG_ID>);

  ProductFeedFields::NAME => 'Test Feed',
  ProductFeedFields::SCHEDULE => array(
    ProductFeedScheduleFields::INTERVAL => 'DAILY',
    ProductFeedScheduleFields::URL =>'',
    ProductFeedScheduleFields::HOUR => 22,

from facebookads.adobjects.productfeed import ProductFeed

product_feed = ProductFeed(parent_id=<PRODUCT_CATALOG_ID>)

product_feed[] = 'Test Feed'
product_feed[ProductFeed.Field.schedule] = {
    'interval': 'DAILY',
    'url': '',
    'hour': 22,

ProductFeed productFeed = new ProductCatalog(<PRODUCT_CATALOG_ID>, context).createProductFeed()
  .setName("Test Feed")
String product_feed_id = productFeed.getId();
curl \
  -F 'name=Test Feed' \
  -F 'schedule={ 
    "interval": "DAILY", 
    "url": "http:\/\/\/sample_feed.tsv", 
    "hour": 22 
  }' \
  -F 'access_token=<ACCESS_TOKEN>' \<PRODUCT_CATALOG_ID>/product_feeds

Optional: you can update hotel room type and pricing configurations based on various check-in dates and length of stay more frequently through /PRODUCT_CATALOG_ID>/hotel_rooms_batch and /PRODUCT_CATALOG_ID>/pricing_variables_batch. By providing this dynamic pricing, you can automatically serve up ads with date-based availability and pricing based on where and when they want to go. More details in Batch Upload Hotel Room Types and Batch Upload Pricing Variables.

Create Hotel Set

Once your catalog is set up, you'll need to create a hotel set which contains a list of hotel properties to advertise for:

use FacebookAds\Object\ProductSet;
use FacebookAds\Object\Fields\ProductSetFields;

$hotel_set = new ProductSet(null, <PRODUCT_CATALOG_ID>);

  ProductSetFields::NAME => 'Test Hotel Set',
  ProductSetFields::FILTER => array(
    'brand' => array(
      'i_contains' => 'sample brand',

from facebookads.adobjects.productset import ProductSet

hotel_set = ProductSet(None, <PRODUCT_CATALOG_ID>)

hotel_set[] = 'Test Hotel Set'
hotel_set[ProductSet.Field.filter] = {
    'brand': {
        'i_contains': 'sample brand',

curl \
  -F 'name=Test Hotel Set' \
  -F 'filter={"brand":{"i_contains":"sample brand"}}' \
  -F 'access_token=<ACCESS_TOKEN>' \<PRODUCT_CATALOG_ID>/product_sets

2. Build Audience

The travel audiences you build for targeting is based on user intent from your website and mobile app. At a minimum, make sure to implement Search, ViewContent, InitiateCheckout and Purchase standard events on your website and app.

User Signals

Below is an example of the pixel code needed to send the Search event from a search results page. The complete documentation is here:

<!-- Facebook Pixel Code -->

// Insert Your Facebook Pixel ID below. 
fbq('init', '<FB_PIXEL_ID>');

fbq('track', 'Search', { 
content_type: 'hotel', 
content_ids: ['1234', '2345', '3456', '4567'], // top search results
checkin_date: '2016-02-12', 
checkout_date: '2016-02-19', 
destination: 'Upper East Side, Manhattan, New York City, New York, US',
city: 'New York City', //recommended
region: 'New York', // region is the state for the US
country: 'US', //recommended
} );
<!-- End Facebook Pixel Code -->

Associate User Signals to Catalogs

Next, you need to associate the user signals from pixel and App Events with the hotel catalogs that consume this data:

use FacebookAds\Object\ProductCatalog;

$product_catalog = new ProductCatalog(<PRODUCT_CATALOG_ID>);
$product_catalog->createExternalEventSource(array(), array(
  'external_event_sources' => array(
from facebookads.adobjects.productcatalog import ProductCatalog

product_catalog = ProductCatalog(<PRODUCT_CATALOG_ID>)
curl \
  -F 'external_event_sources=["<PIXEL_ID>","<APP_ID>"]' \
  -F 'access_token=<ACCESS_TOKEN>' \<PRODUCT_CATALOG_ID>/external_event_sources

Create Event Source Group

You then create an event source group to join all of your sources for user signals together:

use FacebookAds\Object\EventSourceGroup;
use FacebookAds\Object\Fields\EventSourceGroupFields;

$event_source_group = new EventSourceGroup(null, <BUSINESS_ID>);
  EventSourceGroupFields::NAME => 'My Travel Company Events',
  EventSourceGroupFields::EVENT_SOURCES => array(

from facebookads.adobjects.eventsourcegroup import EventSourceGroup

event_source_group = EventSourceGroup(parent_id=<BUSINESS_ID>)
event_source_group[] = 'My Travel Company Events'
event_source_group[EventSourceGroup.Field.event_sources] = [

curl \
  -F 'name=My Travel Company Events' \
  -F 'event_sources=["<PIXEL_ID>","<APP_ID>"]' \
  -F 'access_token=<ACCESS_TOKEN>' \<BUSINESS_ID>/event_source_groups

Share this event source group to any ad accounts that will advertise to this audience:

use FacebookAds\Object\EventSourceGroup;

$event_source_group = new EventSourceGroup(<EVENT_SOURCE_GROUP_ID>);
// ad account id without 'act_'
$event_source_group->createSharedAccount(array(), array(
  'accounts' => array(
from facebookads.adobjects.eventsourcegroup import EventSourceGroup

event_source_group = EventSourceGroup(<EVENT_SOURCE_GROUP_ID>)
# ad account id without 'act_'
response = event_source_group.create_shared_account(
        'accounts': ['<ACCOUNT_ID_WITHOUT_ACT>'],
curl \
  -F 'accounts=["<ACCOUNT_ID_WITHOUT_ACT>"]' \
  -F 'access_token=<ACCESS_TOKEN>' \<EVENT_SOURCE_GROUP_ID>/shared_accounts

Create Travel Audience

Finally, you'll need to create a travel audience of people who will see your ads. Below is an example of a travel audience targeting people who searched hotels in 'New York City' more than 3 times in the past 2 days but haven't booked yet:

use FacebookAds\Object\CustomAudience;
use FacebookAds\Object\Fields\CustomAudienceFields;
use FacebookAds\Object\Values\CustomAudienceSubtypeValues;
use FacebookAds\Object\Values\CustomAudienceClaimObjectiveValues;
use FacebookAds\Object\Values\CustomAudienceContentTypeValues;

$custom_audience = new CustomAudience(null, 'act_<AD_ACCOUNT_ID>');
  CustomAudienceFields::NAME => 'Travel Audience',
  CustomAudienceFields::SUBTYPE => CustomAudienceSubtypeValues::CLAIM,
  CustomAudienceFields::CLAIM_OBJECTIVE =>
  CustomAudienceFields::CONTENT_TYPE => CustomAudienceContentTypeValues::HOTEL,
  CustomAudienceFields::RULE => array(
    'destination' => array('i_contains' => 'New York City'),
  'inclusions' => array(
      'event' => 'Search',
      'count' => array('gt' => 3),
      'retention' => array('min_seconds'=> 0, 'max_seconds'=> 172800),
  'exclusions' => array(
      'event' => 'Purchase',
      'retention' => array('min_seconds'=> 0, 'max_seconds'=> 172800),
from facebookads.adobjects.customaudience import CustomAudience

audience = CustomAudience(parent_id='act_<AD_ACCOUNT_ID>')
audience[] = 'Travel Audience'
audience[CustomAudience.Field.subtype] = CustomAudience.Subtype.claim
audience[CustomAudience.Field.claim_objective] = \
audience[CustomAudience.Field.content_type] = CustomAudience.ContentType.hotel
audience[CustomAudience.Field.event_source_group] = <EVENT_SOURCE_GROUP_ID>
audience[CustomAudience.Field.rule] = {
    'destination': {'i_contains': 'New York City'},
audience['inclusions'] = [
        'event': 'Search',
        'count': {'gt': 3},
        'retention': {'min_seconds': 0, 'max_seconds': 172800},
audience['exclusions'] = [
        'event': 'Purchase',
        'retention': {'min_seconds': 0, 'max_seconds': 172800},

curl \
  -F 'name=Travel Audience' \
  -F 'subtype=CLAIM' \
  -F 'claim_objective=TRAVEL' \
  -F 'content_type=HOTEL' \
  -F 'event_source_group=<EVENT_SOURCE_GROUP_ID>' \
  -F 'rule={"destination":{"i_contains":"New York City"}}' \
  -F 'inclusions=[ 
      "event": "Search", 
      "count": {"gt":3}, 
      "retention": {"min_seconds":0,"max_seconds":172800} 
  ]' \
  -F 'exclusions=[{"event":"Purchase","retention":{"min_seconds":0,"max_seconds":172800}}]' \
  -F 'access_token=<ACCESS_TOKEN>' \<AD_ACCOUNT_ID>/customaudiences

Travel audiences start populating from the moment you successfully create them. Make sure to check your travel audience size to confirm that it is set up and populating correctly.

After you create your audience, you can add it to the targeting spec in your Dynamic Ads for travel campaign which we'll explain in 3. Create Ads

3. Create Ads

Dynamic Ads for travel uses the PRODUCT_CATALOG_SALES objective.

Create Campaign

Make sure you specify a hotel catalog in promoted_object at the campaign level.

use FacebookAds\Object\Campaign;
use FacebookAds\Object\Fields\CampaignFields;
use FacebookAds\Object\Values\CampaignObjectiveValues;

$campaign = new Campaign(null, 'act_<AD_ACCOUNT_ID>');
  CampaignFields::NAME => 'Product Catalog Sales Campaign',
  CampaignFields::OBJECTIVE => CampaignObjectiveValues::PRODUCT_CATALOG_SALES,
  CampaignFields::PROMOTED_OBJECT => array(
    'product_catalog_id' => <PRODUCT_CATALOG_ID>,

from facebookads.adobjects.campaign import Campaign

campaign = Campaign(parent_id='act_<AD_ACCOUNT_ID>')
campaign[] = 'Product Catalog Sales Campaign'
objective = Campaign.Objective.product_catalog_sales
campaign[Campaign.Field.objective] = objective
campaign[Campaign.Field.promoted_object] = {
    'product_catalog_id': <PRODUCT_CATALOG_ID>,

    'status': Campaign.Status.paused,
Campaign campaign = new AdAccount(act_<AD_ACCOUNT_ID>, context).createCampaign()
  .setName("Product Catalog Sales Campaign Group")
  .setPromotedObject("{\"product_catalog_id\":\"" + <PRODUCT_CATALOG_ID> + "\"}")
String campaign_id = campaign.getId();
curl \
  -F 'name=Product Catalog Sales Campaign' \
  -F 'objective=PRODUCT_CATALOG_SALES' \
  -F 'promoted_object={"product_catalog_id":"<PRODUCT_CATALOG_ID>"}' \
  -F 'status=PAUSED' \
  -F 'access_token=<ACCESS_TOKEN>' \<AD_ACCOUNT_ID>/campaigns

Create Ad Set

Please note that Dynamic Ads for travel does not support inline dynamic audience in targeting spec. You should create the travel audience separately and specify the audience id in dynamic_audience_ids when you create the ad set:

use FacebookAds\Object\AdSet;
use FacebookAds\Object\Fields\AdSetFields;
use FacebookAds\Object\Fields\TargetingFields;
use FacebookAds\Object\Values\AdSetBillingEventValues;
use FacebookAds\Object\Values\AdSetOptimizationGoalValues;

$adset = new AdSet(null, 'act_<AD_ACCOUNT_ID>');

  AdSetFields::NAME => 'Product Catalog Sales Adset',
  AdSetFields::BID_AMOUNT => 3000,
  AdSetFields::BILLING_EVENT => AdSetBillingEventValues::IMPRESSIONS,
  AdSetFields::DAILY_BUDGET => 15000,
  AdSetFields::TARGETING => array(
    TargetingFields::GEO_LOCATIONS => array(
      'countries' => array('US'),
    TargetingFields::DYNAMIC_AUDIENCE_IDS => array(<DYNAMIC_AUDIENCE_ID>),
  AdSetFields::PROMOTED_OBJECT => array(
    'product_set_id' => <PRODUCT_SET_ID>,

from facebookads.adobjects.adset import AdSet
from facebookads.adobjects.targeting import Targeting

adset = AdSet(parent_id=ad_account_id)
adset[] = 'Product Catalog Sales Adset'
adset[AdSet.Field.bid_amount] = 3000
adset[AdSet.Field.billing_event] = AdSet.BillingEvent.impressions
adset[AdSet.Field.optimization_goal] = \
adset[AdSet.Field.daily_budget] = 15000
adset[AdSet.Field.campaign_id] = campaign_id
adset[AdSet.Field.targeting] = {
    Targeting.Field.geo_locations: {
        Targeting.Field.countries: ['US'],
    Targeting.Field.dynamic_audience_ids: [
adset[AdSet.Field.promoted_object] = {
    'product_set_id': product_set_id,

AdSet adSet = new AdAccount(act_<AD_ACCOUNT_ID>, context).createAdSet()
  .setName("Product Catalog Sales Adset")
    new Targeting()
        new TargetingGeoLocation()
  .setPromotedObject("{\"product_set_id\":\"" + <PRODUCT_SET_ID> + "\"}")
String ad_set_id = adSet.getId();
curl \
  -F 'name=Product Catalog Sales Adset' \
  -F 'bid_amount=3000' \
  -F 'billing_event=IMPRESSIONS' \
  -F 'optimization_goal=OFFSITE_CONVERSIONS' \
  -F 'daily_budget=15000' \
  -F 'campaign_id=<CAMPAIGN_ID>' \
  -F 'targeting={ 
    "geo_locations": {"countries":["US"]}, 
    "dynamic_audience_ids": ["<DYNAMIC_AUDIENCE_ID>"] 
  }' \
  -F 'promoted_object={"product_set_id":"<PRODUCT_SET_ID>"}' \
  -F 'status=PAUSED' \
  -F 'access_token=<ACCESS_TOKEN>' \<AD_ACCOUNT_ID>/adsets

Ad Creative Template

When you provide a creative template for Dynamic Ads for travel, you can add template tags that are used at runtime based on data in your hotel catalog and user intent. You can leverage template_url to dynamically construct the hotel url using those template tags as well, this will give you more flexibility than using url from the catalog. If template_url cannot be constructed at ads rendering time (e.g. due to missing checkin/check out dates), we will fall back to the url from the hotel catalog. See documentation for a complete list of template tags.

use FacebookAds\Object\AdCreative;
use FacebookAds\Object\Fields\AdCreativeFields;
use FacebookAds\Object\Fields\AdCreativeLinkDataFields;
use FacebookAds\Object\Fields\AdCreativeObjectStorySpecFields;
use FacebookAds\Object\Values\AdCreativeCallToActionTypeValues;
use FacebookAds\Object\AdCreativeObjectStorySpec;
use FacebookAds\Object\AdCreativeLinkData;

$object_story_spec = new AdCreativeObjectStorySpec();
  AdCreativeObjectStorySpecFields::PAGE_ID => <PAGE_ID>,
  AdCreativeObjectStorySpecFields::TEMPLATE_DATA =>
    (new AdCreativeLinkData())->setData(array(
      AdCreativeLinkDataFields::MESSAGE =>
        'Book your upcoming stay in {{}}',
      AdCreativeLinkDataFields::NAME => '{{ | titleize}}',
      AdCreativeLinkDataFields::LINK => '<LINK>',
      AdCreativeLinkDataFields::DESCRIPTION =>
      AdCreativeLinkDataFields::ADDITIONAL_IMAGE_INDEX => 0,
      AdCreativeLinkDataFields::CALL_TO_ACTION => array(
        'type' => AdCreativeCallToActionTypeValues::BOOK_TRAVEL,

$creative = new AdCreative(null, 'act_<AD_ACCOUNT_ID>');
  AdCreativeFields::NAME => 'Dynamic Ad Template Creative Sample',
  AdCreativeFields::OBJECT_STORY_SPEC => $object_story_spec,
  AdCreativeFields::TEMPLATE_URL_SPEC =>
      'config' => array(
        'app_id' => '123456789012345',
      'ios' => array(
        'url' => 'example://home/hotel'.
          '?id={{hotel.hotel_id | urlencode}}'.
          '&startDate={{trip.checkin_date date_format:Y-m-d | urlencode}}'.
          '&endDate={{trip.checkout_date date_format:Y-m-d | urlencode}}',
      'web' => array(
        'url' => ''.
          '?id={{hotel.hotel_id | urlencode}}'.
          '&startDate={{trip.checkin_date date_format:Y-m-d | urlencode}}'.
          '&endDate={{trip.checkout_date date_format:Y-m-d | urlencode}}',
  AdCreativeFields::PRODUCT_SET_ID => <PRODUCT_SET_ID>,

from facebookads.adobjects.adcreative import AdCreative
from facebookads.adobjects.adcreativeobjectstoryspec \
    import AdCreativeObjectStorySpec

story = AdCreativeObjectStorySpec()
story[story.Field.page_id] = <PAGE_ID>
story[story.Field.template_data] = {
    'message': 'Book your upcoming stay in {{}}',
    'name': '{{ | titleize}}',
    'link': '<LINK>',
    'description': '{{hotel.description}}',
    'additional_image_index': 0,
    'call_to_action': {
        'type': 'BOOK_TRAVEL',

creative = AdCreative(parent_id='act_<AD_ACCOUNT_ID>')
creative[] = 'Dynamic Ad Template Creative Sample'
creative[AdCreative.Field.object_story_spec] = story
template_url = ('{{hotel.hotel_id}}'
                '&startDate={{trip.checkin_date date_format:Y-m-d}}'
                '&endDate={{trip.checkout_date date_format:Y-m-d}}')
creative[AdCreative.Field.template_url] = template_url
creative[AdCreative.Field.product_set_id] = <PRODUCT_SET_ID>
curl \
  -F 'name=Dynamic Ad Template Creative Sample' \
  -F 'object_story_spec={ 
    "page_id": "<PAGE_ID>", 
    "template_data": { 
      "additional_image_index": 0, 
      "call_to_action": {"type":"BOOK_TRAVEL"}, 
      "description": "{{hotel.description}}", 
      "link": "<LINK>", 
      "message": "Book your upcoming stay in {{}}", 
      "name": "{{ | titleize}}" 
  }' \
  -F 'template_url_spec={ 
    "config": {"app_id":"123456789012345"}, 
    "ios": { 
      "url": "example:\/\/home\/hotel?id={{hotel.hotel_id | urlencode}}&startDate={{trip.checkin_date date_format:Y-m-d | urlencode}}&endDate={{trip.checkout_date date_format:Y-m-d | urlencode}}" 
    "web": { 
      "url": "http:\/\/\/hotel?id={{hotel.hotel_id | urlencode}}&startDate={{trip.checkin_date date_format:Y-m-d | urlencode}}&endDate={{trip.checkout_date date_format:Y-m-d | urlencode}}" 
  }' \
  -F 'product_set_id=<PRODUCT_SET_ID>' \
  -F 'access_token=<ACCESS_TOKEN>' \<AD_ACCOUNT_ID>/adcreatives

Create Ad

Finally create your ad that delivers your dynamic creative:

use FacebookAds\Object\Ad;
use FacebookAds\Object\Fields\AdFields;

$data = array(
  AdFields::NAME => 'My Ad',
  AdFields::ADSET_ID => <AD_SET_ID>,
  AdFields::CREATIVE => array(
    'creative_id' => <CREATIVE_ID>,

$ad = new Ad(null, 'act_<AD_ACCOUNT_ID>');
from import Ad

ad = Ad(parent_id='act_<AD_ACCOUNT_ID>')
ad[] = 'My Ad'
ad[Ad.Field.adset_id] = <AD_SET_ID>
ad[Ad.Field.creative] = {
    'creative_id': <CREATIVE_ID>,
    'status': Ad.Status.paused,
Ad ad = new AdAccount(act_<AD_ACCOUNT_ID>, context).createAd()
  .setName("My Ad")
    new AdCreative()
ad_account = FacebookAds::AdAccount.get('act_<AD_ACCOUNT_ID>')
ad ={
  name: 'My Ad',
  adset_id: <ADSET_ID>,
  creative: {
    creative_id: <CREATIVE_ID>,
  status: 'PAUSED',
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>' \<AD_ACCOUNT_ID>/ads

Congratulations! You have just created your first Dynamic Ad for travel. For more details, the complete set of documentation is available here.