Overview
Welcome to the Dealer.com Website Integration API! This API performs common actions on Dealer.com websites, such as loading scripts, adding CSS and inserting markup into common locations.
As this is a browser based API, it is written in JavaScript and has simple data contracts and interfaces.
Requirements
Integrated Partner Program
The use of this API requires that you are in communication with our Integrated Partner Program. To find out more information or to enroll, please contact us at IntegratedPartners@coxautoinc.com or fill out this form.
Once enrolled, you will be provided with an integration key to use when making API calls.
Bootstrapping Your Script
Start by creating a simple JavaScript file that registers with the API and loads additional scripts or styling if necessary. You can leverage the available API capabilities in your script to place content on pages, or include existing integration code if necessary. Your script should eventually be hosted on a content delivery network such as Cloudfront or Akamai, but for development purposes you can host it anywhere.
When you begin development of your script, it's easy to test on any Dealer.com site to see how it will function. To accomplish that, follow these steps:
1.) Load any Dealer.com site where you want to test your integration. If you don't already have a site in mind, a good one to start with is https://www.roimotors.com/.
2.) Open the developer tools in your browser and enter this code in the Console window:
(async APILoader => {
const API = await APILoader.create();
API.test('https://www.yourdomain.com/your-javascript-file.js');
})(window.DDC.APILoader);
This will set a cookie in your browser and instruct the site to load your defined script as if the integration was enabled. The code will only load for you in the current browser session so that you are able to iterate and test your code. When it is ready for activation on sites, we can set the integration up in our system and load your script for all users of the site(s) where it should be enabled. The set cookie expires when your browser session expires or you clear your browser cookies.
3.) Add the ?_integrationMode=debug
URL parameter to the page you are testing to activate the cookied script for your page view. This will also provide an additional benefit of any logged messages in your script being output to the browser console for troubleshooting and debugging purposes.
Other methods:
You can accomplish the same test setup as above by installing a browser extension such as Custom JavaScript for web sites and loading your script using that mechanism. This will not require the
?_integrationMode=debug
parameter for the code to be executed on the site, however it's a good idea to use that parameter either way for better debugging output in your browser console.Alternate to both of the above methods, we can create a test site for you and specify the script to include on the site. This will make your integrated code available for all viewers of the test site which is useful in a setting where multiple stakeholders or developers want to see the integration in development or for testing purposes.
Your code should be minimal and perform only actions necessary to bootstrap your integration on to the site. See the Technical Requirements below for more detail on this point.
Technical Requirements
This is an example of an immediately executing function expression:
(async APILoader => {
const API = await APILoader.create();
// API.subscribe(...);
// Your code here
})(window.DDC.APILoader);
Avoid Global Scope Pollution - Always ecapsulate your code in an immediately executing function expression to avoid polluting the global namespace. See sidebar for example of such a function.
Avoid Duplicate Code - Please refrain from including large libraries such as jQuery or React, as both are already available on all pages of all of our sites. Avoid polyfilling items that DDC already polyfills.
Minification and Compression - All code should be minified and served using gzip or better compression.
Minimize Asset Size - Integrations should avoid loading excessive imagery, fonts and scripts. While some are often necessary, less is more and avoiding loading duplicate scripts will help reduce load time for your integration as well as improve overall website speed.
Minimize Domains - Serve content from as few domains as possible to reduce the number of https connections the browser must make.
Minimize Files - Serve content from as few files as possible to allow for optimal downloads.
Minimize Requests - Requests to services necessary for your integration to function should be as minimal as possible. For example, on a search results page with 30 vehicles, you should make a single batch request to gather data for those vehicles rather than 30 individual requests.
Delay Load Content - A good way to optimize your integration is to do less work on initial page load, and to only do the remaining work on demand. For example, if your integration inserts a Call To Action button, banner or other "point of entry" and when clicked that opens your tool in an overlay, consider waiting to load the code for that overlay until the user clicks the button. You can leverage the API to insert the button with a very small amount of code. When clicked, a function can be called to load the remaining code for your overlay and then fire the initialization routine. This may not be a viable solution for all integrations, but this pattern should be followed whenever possible.
Cache Service Responses - You should set a proper caching policy on services used by your integration to avoid having to make the same request multiple times to a service. Additionally, you could consider leveraging local storage or session storage to cache data from calls to services. Similar to the line item above, if you have a service that returns data, caching that data in the user's browser through a caching policy or local storage will avoid having to request it multiple times. This is helpful when users search for vehicles on a site and may see the same vehicle multiple times in different contexts. Reduced calls to your service will reduce network traffic, stress on your service, and will improve the user experience.
Use a CDN - Your bootstrap script and any other integration code or assets should be located on a highly available content delivery network and must work over an HTTPS connection.
Browser Support - We strive to support Edge, Firefox, Chrome, Safari, and iOS Safari, however, you may choose your own level of support. We do not allow access to our web sites on Legacy Edge, Internet Explorer 11 or lower IE versions.
Events
By subscribing to events we publish, you can easily obtain the data you need to integrate your solution into the website. These events provide data about the website your integration is loading on and the current page the user is viewing.
There are currently three types of events and each has a consistent data format.
Event Types
Page Event
Example Payload
{
accountId: 'futuredemodealer',
siteId: 'futuredemodealer',
defaultDomain: 'www.roimotors.com',
indexPage: true,
searchPage: false,
detailPage: false,
franchises: ['honda'],
design: {
variationId: 'v9_GLOBAL_0011_V2',
themekit: 'BLUE_WHITE'
},
pageName: 'INDEX',
layoutType: 'desktop',
locale: 'en_US'
}
Field Key | Example Value | Field Format |
---|---|---|
accountId |
futuredemodealer |
String |
siteId |
futuredemodealer |
String |
defaultDomain |
www.roimotors.com |
String |
indexPage |
true |
Boolean |
searchPage |
false |
Boolean |
detailPage |
false |
Boolean |
design |
{variationId: 'v9_GLOBAL_0011_V2', themekit: 'BLUE_WHITE'} |
Object |
franchises |
['honda'] |
Array |
pageName |
INDEX |
String |
layoutType |
desktop |
String |
locale |
en_US |
String |
Dealership Info Event
Example Payload
{
dealershipName: 'ROI Motors',
dealershipAddress1: '1 Howard Street',
dealershipAddress2: '',
dealershipCity: 'Burlington',
dealershipCodes: {
'dealertrack-post': 'futuredemodealer',
dtid: '12345',
'dt-dr-profile': 'futuredemodealer',
affiliate_promotions: 'avis',
'at-kbb': '12345678'
},
dealershipFranchises: ['honda'],
dealershipPostalCode: '05401',
dealershipStateProvince: 'VT',
dealershipCountry: 'US'
}
Field Key | Example Value | Field Format |
---|---|---|
dealershipName |
ROI Motors |
String |
dealershipAddress1 |
1 Howard Street |
String |
dealershipAddress2 |
String |
|
dealershipCity |
Burlington |
String |
dealershipCodes |
See Example |
Object |
dealershipFranchises |
['honda'] |
Array |
dealershipPostalCode |
05401 |
String |
dealershipStateProvince |
VT |
String |
dealershipCountry |
US |
String |
Vehicle Event
Example Payload
{
accountId: 'futuredemodealer',
address: {
accountName: 'ROI Motors',
city: 'Burlington',
state: 'VT',
postalCode: '05401',
country: 'US'
},
autodataCaId: '1234567',
bodyStyle: 'SUV',
certified: false,
chromeId: '411601',
cityFuelEconomy: 28,
classification: 'primary',
dealerCodes: {
'dealertrack-post': 'futuredemodealer',
dtid: '12345',
'dt-dr-profile': 'futuredemodealer',
affiliate_promotions: 'avis',
'at-kbb': '12345678'
},
driveLine: 'Front-wheel Drive',
engine: 'I-4 cyl',
engineSize: '1.5L',
deliveryDateRange: '10/30/2028 - 11/15/2028',
exteriorColor: 'Crystal Black Pearl',
finalPrice: 32000,
fuelType: 'Regular Unleaded',
highestPrice: 34500,
highwayFuelEconomy: 34,
images: [
'https://pictures.dealer.com/f/futuredemodealer/1182/0686eb936bd7a4905f751493cc28dcb9x.jpg'
],
internetPrice: 33000,
interiorColor: 'White',
inventoryType: 'new',
link: 'https://www.roimotors.com/new/Honda/2020-Honda-Accord-burlington-ab119e0e0a0a00f944d6f3031cd34854.htm',
make: 'Honda',
model: 'CR-V',
modelCode: 'RW1H9LKNW',
msrp: 34000,
odometer: 3,
optionCodes: [
'ABC',
'123',
'321'
],
startingPrice: 34000,
status: 'live',
stockNumber: '00100060',
transmission: 'Variable',
trim: 'Touring 2WD',
uuid: 'ab119e0e0a0a00f944d6f3031cd34854',
vin: '1HGCV1F42JA141468',
year: 2020
}
Vehicle pricing is a particularly flexible piece of our system. With flexibility often comes complexity, so we provide several fields on the vehicle object to simplify and standardize the pricing information for the API consumer.
startingPrice
is the first pricing value displayed on the vehicle. It is often the highest price displayed to the user. In some cases a dealer fee can increase the price later in the pricing stack.
finalPrice
is the lowest price displayed on the vehicle not including conditional incentives. This is intended to be the price that the dealership is willing to sell the vehicle at, less any special offers that are only applicable to some customers.
highestPrice
is the greater of all of the available pricing field values. It is typically the highest price shown to the user on the page, but in some cases can be higher than what is displayed to the user. This field is deprecated
and may be removed at a later date with sufficient notice.
It is recommended that you use startingPrice
for the most expensive price displayed to the user and finalPrice
for the least expensive price displayed to the user.
You may notice one or more of these fields available in the vehicle object on some sites:
askingPrice
internetPrice
msrp
retailValue
salePrice
All of the above fields are deprecated
, meaning they may be removed from the API at a later date. New integrations should not use any of the above fields and should instead rely on startingPrice
or finalPrice
for the correct vehicle pricing information.
Please note that all of our prices are based on the available marketing context for the current vehicle. The same vehicle can be marketed in multiple contexts and display unique prices in those contexts. The API will always return the contextually appropriate price for the current vehicle.
Field Key | Example Value | Field Format | Status |
---|---|---|---|
accountId |
futuredemodealer |
String |
|
address |
{"accountName": "ROI Motors", "city": "Burlington", "state": "VT", "postalCode": "05401", "country": "US"} |
Object |
|
askingPrice |
32000 |
Integer |
Deprecated |
autodataCaId |
CAD00CHT27CG0 |
String |
|
bodyStyle |
Sedan |
String |
|
certified |
false |
Boolean |
|
chromeId |
407123 |
String |
|
cityFuelEconomy |
30 |
Integer |
|
classification |
primary |
String |
|
dealerCodes |
{"dealertrack-post": "futuredemodealer", "dtid": "12345", "dt-dr-profile": "futuredemodealer"} |
Object |
|
deliveryDateRange |
10/30/2028 - 11/15/2028 |
String |
|
doors |
4-door |
String |
|
driveLine |
FWD |
String |
|
engine |
I-4 cyl |
String |
|
engineSize |
1.5L |
String |
|
exteriorColor |
Platinum White Pearl |
String |
|
finalPrice |
32000 |
Integer |
|
fuelType |
Regular Unleaded |
String |
|
highestPrice |
34500 |
Integer |
Deprecated |
highwayFuelEconomy |
38 |
Integer |
|
images |
["https://pictures.dealer.com/1.jpg"] |
Array |
|
interiorColor |
Black |
String |
|
internetPrice |
33000 |
Integer |
Deprecated |
inventoryType |
new |
String |
|
link |
https://www.roimotors.com/new/Honda/2020-Honda-Accord-burlington-ab119e0e0a0a00f944d6f3031cd34854.htm |
String |
|
make |
Honda |
String |
|
model |
Accord |
String |
|
modelCode |
RW1H9LKNW |
String |
|
msrp |
34000 |
Integer |
Deprecated |
odometer |
5 |
Integer |
|
oemSourcedMerchandisingStatus |
Reserved |
String |
|
optionCodes |
["ABC", "123", "321"] |
Array |
|
retailValue |
32000 |
Integer |
Deprecated |
salePrice |
32000 |
Integer |
Deprecated |
startingPrice |
34000 |
Integer |
|
status |
live |
String |
|
stockNumber |
00180772 |
String |
|
transmission |
Variable |
String |
|
trim |
EX |
String |
|
uuid |
ab119e0e0a0a00f944d6f3031cd34854 |
String |
|
vin |
1HGCV1F42JA141468 |
String |
|
year |
2020 |
Integer |
Event Subscriptions
To receive data for events, you must opt-in to event subscriptions. Each event is named and versioned to ensure it continues to operate in a consistent manner over time. As newer events supercede these, older events may be deprecated but will not be removed if still in use.
Page Load V1
Usage
(async APILoader => {
const API = await APILoader.create();
API.subscribe('page-load-v1', ev => {
API.log(ev);
});
})(window.DDC.APILoader);
Parameter Name | Example Value | Parameter Type |
---|---|---|
event-id |
page-load-v1 |
String |
callback |
ev => { API.log(ev); } |
function |
This event passes the standard Page Event object to your callback function.
The page load event is useful to determine the context of the current page. By mapping our siteId or domain field to a customer ID in your system, you can determine the site your code is executing on as well as relevant information about the current page type and user's device type. This approach eliminates the need for configuration in our system beyond simply enabling or disabling your integration for each site.
Dealership Info V1
Usage
(async APILoader => {
const API = await APILoader.create();
API.subscribe('dealership-info-v1', ev => {
API.log(ev);
});
})(window.DDC.APILoader);
Parameter Name | Example Value | Parameter Type |
---|---|---|
event-id |
dealership-info-v1 |
String |
callback |
ev => { API.log(ev); } |
function |
This event passes the standard Dealership Info Event object to your callback function.
The dealership info event is useful if you need to know the name and address of the dealership.
Vehicle Shown V1
Usage
(async APILoader => {
const API = await APILoader.create();
API.subscribe('vehicle-shown-v1', ev => {
API.log(ev);
});
})(window.DDC.APILoader);
Parameter Name | Example Value | Parameter Type |
---|---|---|
event-id |
vehicle-shown-v1 |
String |
callback |
ev => { API.log(ev); } |
function |
This event passes the standard Vehicle Event object to your callback function.
This event is sent for each vehicle present on the current page. For search results pages, a dozen or more such events may be fired. In the future, dynamic in-page updates to vehicle results will cause potentially hundreds of events to be fired on a single page view as new vehicles are displayed.
On a vehicle deals page, a single event is fired because you are viewing a single vehicle. This is useful for capturing details about the vehicles that a user has viewed, or to take a particular action for a certain type or class of vehicles.
Vehicle Data Updated V1
Usage
(async APILoader => {
const API = await APILoader.create();
API.subscribe('vehicle-data-updated-v1', data => {
API.log(data.payload.pageData); // Outputs the Page Data object to the console.
API.log(data.payload.vehicleData); // Outputs the updated Vehicle Data object to the console.
});
})(window.DDC.APILoader);
Parameter Name | Example Value | Parameter Type |
---|---|---|
event-id |
vehicle-data-updated-v1 |
String |
callback |
ev => { API.log(ev); } |
function |
This event passes the standard Page Event object to your callback function as well as the full array of Vehicle Event data objects.
This is useful for coding your application to work with our modern Search Results Page which is a dynamic single page application. When the list of vehicles is refreshed/updated, this event is fired with the Page and Vehicle payload of data for you to use as needed. Any subsequent methods/subscriptions/insertions can occur within this event, to ensure that when vehicles are updated your code immediately executes to decorate those vehicle cards with your content.
API Methods
API.subscribe(name, callback(event))
Usage
(async APILoader => {
const API = await APILoader.create();
API.subscribe('event-name-and-version', ev => {
API.log(ev);
});
})(window.DDC.APILoader);
Please see the specific event documentation for more detail on the available events and the data payload sent to your callback function.
API.insertCallToAction(type, intent, setupFunction(meta))
Usage
(async APILoader => {
const API = await APILoader.create();
API.insertCallToAction('button', 'value-a-trade', meta => {
return {
type: 'default',
href: 'https://www.yourdomain.com/value-a-trade/?vin=' + meta.vin,
target: '_blank',
text: {
en_US: 'Value My Trade',
fr_CA: 'Valeur mon commerce'
}
}
});
})(window.DDC.APILoader);
Designed for use within the Page Load subscription, the insertCallToAction
method is used to create a call to action (CTA) button for placement on web site inventory items. Rather than generating markup and inserting it into a predefined location, when using this method you specify the CTA type (button
is currently the only supported type), an intent (more on this below), and a data object describing the CTA's attributes. The data object follows the same pattern as the object passed into the API.create method which you can use as a reference to see all available config options.
Parameter Name | Purpose | Field Format |
---|---|---|
type |
The type of CTA that should be inserted. | String |
intent |
The intention of the CTA you are inserting. | String |
setupFunction(meta) |
The payload object for the current vehicle. You return a data object from this method. | Object |
setupFunction
is called for each inventory item presented on the page. The meta
field provided is the Vehicle Event payload. You can use the vehicle data to construct an object describing your CTA's attributes, then return it back to the API. With the data returned from setupFunction
, the API creates the markup for your button and places it into the CTA area on each vehicle card on the search results page and/or the CTA area on the vehicle details page. The placement depends on how you set up your code and the integration's configuration options for the given site.
This method acts as an event subscription, so as the application displays new vehicles dynamically (a single page application), new events are fired and setupFunction
is automatically called for each of those new items. This works well for a basic use case where you want to place content on every item having the target location, or every item matching specific criteria available to you in the setupFunction
payload. If you need to execute intermediary code before determining if you need to insert content, such as calling an external service, you should use the insertCallToActionOnce
method instead. You should also always use insertCallToActionOnce
instead of insertCallToAction
when running the code from within a Vehicle Data Updated subscription.
The default location for a CTA is the bottom of the existing CTA list on vehicle search results and details pages.
However, by using this method it enables Dealer.com to reorder the location of your CTA in the list and even map it to "take over" an existing CTA. For example, if a dealer has a custom styled E-Price button and wants your CTA to replace the standard functionality for that feature on their site, we can map your CTA to replace the default functionality. If your code fails to load for some reason, the default behavior of that button still takes effect and ensures that site users can still submit leads, etc.
CTA Type
The current supported type is button
, though other types may be added in the future. We currently only support text-based buttons.
CTA Intent
Button Intent is a concept used in the API as a way to categorize the type of functionality your CTA provides. The current supported intent types are:
chat
check-availability
delivery
digital-retailing
eprice
payment-calculator
pre-approval
request-a-quote
reserve-it-now
send-to-phone
social
test-drive
text-us
value-a-trade
window-sticker
If you have a CTA that does not align with one of these types, please let us know and we will consider adding it to the API.
Callback Format
The callback function for this method is used to create a data object describing the CTA you want to place on the page. The format of this object is described below.
Field Name | Purpose | Example Value(s) | Field Format |
---|---|---|---|
type |
The style of CTA that should be inserted. | default , primary |
String |
classes |
A space delimited list of class names to add to the CTA. | custom-class1 custom-class2 |
String |
href |
The URL to access when the CTA is clicked. Must begin with https:// . |
https://www.yourdomain.com/ |
String |
target |
The link target. | _blank , _self |
String |
onclick |
A function to attach as a click handler to the CTA. | window.MyIntegration.clickFunction |
Function |
text |
An object supplying text for the CTA in one or more languages. en_US is required at minimum. fr_CA is highly recommended. |
See usage example | Object |
attributes |
An object of data- attributes to add to the CTA. |
String |
After creating the callback object, you must then return it for the API to create your CTA. If you do not return anything or return null
, no CTA will be inserted for that vehicle item.
API.insertCallToActionOnce(type, intent, setupFunction(meta))
Functional example
(async APILoader => {
// Initialize an instance of the API
const API = await APILoader.create();
// Receive a notification each time vehicle data is updated on the page (or a new page is loaded).
API.subscribe('vehicle-data-updated-v1', ev => {
// Collect the VIN for each vehicle on the page in an array.
API.utils.getAttributeForVehicles('vin').then(vins => {
API.log("Calling service with these VINs: " + vins.join(','));
// Fetch data from your endpoint by supplying the list of VINs.
fetch('https://www.yourdomain.com/api/endpoint-that-returns-json?vins=' + vins.join(','))
.then(response => {
return response.json();
})
.then(serviceData => {
// Now that you have your service data, you can determine whether or not to place a CTA for this vehicle on the page.
API.insertCallToActionOnce('button', 'value-a-trade', meta => {
if (serviceData.hasOwnProperty(meta.vin)) {
return {
type: 'default',
classes: 'custom-class1 custom-class2',
href: 'https://www.yourdomain.com/value-a-trade/?vin=' + meta.vin,
target: '_blank',
text: {
en_US: 'Value My Trade',
fr_CA: 'Valeur mon commerce'
}
}
} else {
API.log("Skipping vehicle " + meta.vin + " because it does not have service data.");
}
});
});
});
});
})(window.DDC.APILoader);
You may prefer to only insert a CTA when you are ready, after performing other functions. For example, if you need to make a service call to your system with a list of vehicles to determine which ones have data on your side, and only then add CTAs to supported vehicles. With insertCallToActionOnce
, the method behaves as a functional insert which can be chained with other functions, and does not behave as a subscription. With API.insertCallToActionOnce
, you will need to invoke it inside of a vehicle-data-updated-v1
subscription so that your code is triggered each time the list of vehicles is loaded on a page rather than only the first time. You should instead use insertCallToAction
instead of insertCallToActionOnce
when running the code from within a Page Load subscription.
Field Name | Purpose | Field Format |
---|---|---|
type |
The type of CTA that should be inserted. | String |
intent |
The intention of the CTA you are inserting. | String |
setupFunction(meta) |
The payload object for the current vehicle. | Object |
API.insertGalleryContent(target, arrayOfObjects)
Usage
(async APILoader => {
const API = await APILoader.create();
// Callback function to be called when your HTML slide is rendered.
const myCallback = (data) => {
// The `el` property is the top level HTML element where your content will be placed. You may modify this element or insert additional elements into it.
const { el } = data;
// The height and width variables give you size information about the content placement location.
const { width, height } = data;
el.style.height = height;
el.style.width = width;
// The full Vehicle Object is available in the callback data.
const { vehicle } = data;
// This destructures a few variables out of that vehicle object for later use.
// The height and width variables give you size information about the content placement location.
const { year, make, model, vin } = vehicle;
el.innerHTML = `<iframe style="height: ${height}px; width: 100%; border: 0;" frameborder="0" src="https://www.myintegration.com/framed-in-content/index.html?year=${vehicle.year}&make=${vehicle.make}&model=${vehicle.model}&vin=${vehicle.vin}" />`;
}
// This informs your script when the displayed list of vehicles has changed, or when a Vehicle Details Page has loaded.
API.subscribe('vehicle-data-updated-v1', async ev => {
// This obtains a list of VINs for the displayed vehicles.
const vins = await API.utils.getAttributeForVehicles('vin');
// With the list of VINs, you could query your service
// to obtain the dataset of imagery for those vehicles.
// Code to query your service goes here.
const imagesData = await queryYourService(vins);
// With the returned `imagesData`, you could then construct an array of objects in the following format to use when calling `API.insertGalleryContent`.
const imagesToInsert = [
{
vin: '1HGCV1F46LA144134',
insertMethod: 'insert',
images: [
{
type: 'html',
position: 'primary',
callback: myCallback,
src: 'https://via.placeholder.com/530x360.png?text=Primary HTML Content Placeholder'
},
{
type: 'image',
position: 'secondary',
src: 'https://via.placeholder.com/530x360.png?text=Vehicle 1 Secondary Image',
thumbnail: 'https://via.placeholder.com/530x360.png?text=Vehicle 1 Secondary Image',
},
{
type: 'image',
position: 'last',
src: 'https://via.placeholder.com/530x360.png?text=Vehicle 1 Last Image',
thumbnail: 'https://via.placeholder.com/530x360.png?text=Vehicle 1 Last Image',
}
]
},
{
vin: '1HGCV1F48LA139453',
insertMethod: 'replace',
images: [
{
type: 'image',
position: 'primary',
src: 'https://via.placeholder.com/530x360.png?text=Vehicle 2 Primary Image',
thumbnail: 'https://via.placeholder.com/530x360.png?text=Vehicle 2 Primary Image',
},
{
type: 'html',
position: 'secondary',
callback: myCallback,
src: 'https://via.placeholder.com/530x360.png?text=Secondary HTML Content Placeholder'
},
{
type: 'image',
position: 'last',
src: 'https://via.placeholder.com/530x360.png?text=Vehicle 2 Last Image',
thumbnail: 'https://via.placeholder.com/530x360.png?text=Vehicle 2 Last Image',
}
]
}
];
// And finally, call insertGalleryContent with the new imagery data.
API.insertGalleryContent('vehicle-media', imagesToInsert);
});
})(window.DDC.APILoader);
The insertGalleryContent
method allows you to add media to galleries across various pages of Dealer.com sites. The only currently supported target is vehicle-media
, which will insert media into the media carousels on Search Results Pages (Images only) and Vehicle Details Pages (Images and HTML slides).
arrayOfObjects
is an array of objects describing the media to be inserted. Each object requires a vin
field for the target vehicle, an images
array with fields describing the images and other HTML content to insert for that vehicle as well as an optional insertMethod
. The type
field specifies the type of media to be inserted. The available options are image
and html
. Utilizing vehicle-data-updated-v1
to know when the list of vehicles changes combined with API.utils.getAttributeForVehicles
, you can easily obtain a list of the VINs for vehicles shown on the page. With this data, you could query your service to get the dataset of imagery and other content for those vehicles, then construct the array of objects for API.insertGalleryContent
. See the example code for more details on this approach.
The insertMethod
accepts the following string values:
Value | Description |
---|---|
insert |
This is the default value for inserting media. This method inserts the content into the pre-existing media. |
replace |
This method removes all pre-existing media before inserting the new content. |
The images
array objects support the following additional attributes:
Name | Description |
---|---|
type |
Expected values are image or html . The html and callback attributes are only used when type is set to html . |
src |
HTTPS url to the image to be inserted. |
position |
Where to insert the image. primary is used for content that should be displayed to the user as soon as it loads -- as long as the user has not explicitly performed an action to look at pre-existing media. secondary is used for content that should be displayed soon, but not replace the pre-existing main image. last is used to append content to the end of an existing gallery. |
src |
HTTPS url to the image to be inserted. |
thumbnail |
HTTPS url to a thumbnail of the image to be inserted. For performance, it is ideal to provide a low resolution thumbnail that can be used for a preview of the inserted image, however, src will be used if thumbnail is not provided. |
html |
HTML markup to render when the user clicks on the placeholder image. This field is optional, as you can also use the callback function to create your markup. |
callback |
A callback function to call when the user clicks on the placeholder image. This allows you to construct the HTML to display dynamically, with relevant data in context. |
The callback
function you specify is called with a single parameter, which is an object with the following data structure:
Name | Description |
---|---|
el |
{HTMLElement} Initial HTML Element where your markup can be inserted. You can modify this element or insert other content inside of it. |
locationId |
{string} A unique ID for the location where your content will be inserted (either 'carousel' or 'photoswipe'). There are two insert locations on the Vehicle Details page. If your code requires a unique location on the page by ID (some video players may require this), you can add the locationId value to your target ID to make them unique. |
width |
{number} The available width for your target location. This can be useful when constructing iframes to insert, etc. |
height |
{number} The available height for your target location. This can be useful when constructing iframes to insert, etc. |
vehicle |
{object} The Vehicle Object for the current vehicle, so you can use vehicle data when constructing your markup. |
API.insert(name, callback(elem, meta))
Usage
(async APILoader => {
const API = await APILoader.create();
API.insert('location-name', (elem, meta) => {
API.log(elem); // The DOM element where markup may be inserted.
API.log(meta); // The payload object for the current insertion point.
});
})(window.DDC.APILoader);
Designed for use within the Page Load subscription, the insert method allows you to append markup to specific locations on some pages of Dealer sites. These locations are commonly targeted areas where you may want to place content.
When activated, API.insert
will call the callback function you define with the elem
and meta
parameters. It will call this for each relevant location on the page. For example, if you specify vehicle-media
as the location and you are viewing a search results page with 30 vehicles, the callback function you define on API.insert
will be called 30 times, once per vehicle, with the relevant location and vehicle data in the payload.
This acts as an event subscription, so as the application displays new vehicles dynamically (a single page application), new events are fired and your callback is immediately called for each of those new items. This works well for a basic use case where you want to place content on every item having the target location, or every item matching specific criteria available to you in the callback payload.
If you need to execute additional code before determining if you wish to insert content, such as calling an external service, you should use the insertOnce
method instead in combination with the vehicle-data-updated-v1
event as shown in the example here.
API.insertOnce(name, callback(elem, meta))
Usage
(async APILoader => {
const API = await APILoader.create();
// Receive a notification each time vehicle data is updated on the page (or a new page is loaded).
API.subscribe('vehicle-data-updated-v1', ev => {
// Insert content into each vehicle location now present on the page.
API.insertOnce('location-name', (elem, meta) => {
API.log(elem); // The DOM element where markup may be inserted.
API.log(meta); // The payload object for the current insertion point.
});
});
})(window.DDC.APILoader);
Functional example
(async APILoader => {
// Initialize an instance of the API
const API = await APILoader.create();
// Receive a notification each time vehicle data is updated on the page (or a new page is loaded).
API.subscribe('vehicle-data-updated-v1', ev => {
// Collect the VIN for each vehicle on the page in an array.
API.utils.getAttributeForVehicles('vin').then(vins => {
API.log("Calling service with these VINs: " + vins.join(','));
// Fetch data from your endpoint by supplying the list of VINs.
fetch('https://www.yourdomain.com/api/endpoint-that-returns-json?vins=' + vins.join(','))
.then(response => {
return response.json();
})
.then(serviceData => {
// Now that you have your service data, you can determine whether or not to place content for this location on to the page.
API.insertOnce('vehicle-badge', (elem, meta) => {
// Verify my service has data for this vehicle
if (serviceData.hasOwnProperty(meta.vin)) {
// Create your markup here
const div = document.createElement('div');
div.innerText = "Hello World!";
// Insert your markup into the parent element.
API.append(elem, div);
} else {
API.log("Skipping vehicle " + meta.vin + " because it does not have service data.");
}
});
});
});
});
})(window.DDC.APILoader);
You may prefer to only insert content when you are ready, after performing other functions. For example, if you need to make a service call to your system with a list of vehicles to determine which ones have data on your side, and only then decorate specific vehicles with appropriate content. With insertOnce
, the method behaves as a functional insert which can be chained with other functions, and does not behave as a subscription. With API.insertOnce
, you will need to invoke it inside of a vehicle-data-updated-v1
subscription so that your code is triggered each time the list of vehicles is loaded on a page rather than only the first time. You should use insert
instead of insertOnce
when running the code from within a Page Load subscription.
Field Name | Purpose | Field Format |
---|---|---|
location |
The DOM element where the markup should be inserted. | Element |
callback(elem, meta) |
The DOM element where the markup should be inserted, and the payload object for the current insertion point. | Element, Object |
API.updateLink(intent, setupFunction(meta))
The updateLink
method is used to override links on the page where the integration is enabled.
Field Name | Purpose | Field Format |
---|---|---|
intent |
The functionality of the overriding links. | String |
setupFunction(meta) |
The payload object for the current page. | Object |
The current supported intent types are:
payment-search
pre-approval
quote-build
schedule-service
value-a-trade
And, we only support limited attributes of the link to be modified in order to preserve the look and feel of the link.
The attributes that can be modified are href
, target
, onclick
, popover
and attributes (data-*)
.
Usage:
(async APILoader => {
const API = await APILoader.create();
API.updateLink('x-time', meta => {
return {
href: 'https://www.yourdomain.com/?account=' + meta.accountId,
target: '_blank',
}
});
})(window.DDC.APILoader);
API.create(type, options)
Create a Button
(async APILoader => {
const API = await APILoader.create();
const button = API.create('button', {
href: 'https://www.google.com/',
text: {
'en_US': 'Visit Google', // English
'fr_CA': 'Visitez Google', // French
'es_MX': 'Visite Google' // Spanish
},
classes: 'btn btn-primary',
style: 'border: 2px solid #c00',
attributes: {
'data-custom-attribute': 'attribute-value',
'target': '_blank'
},
imgSrc: 'https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png',
imgAlt: {
'en_US': 'Visit Google', // English
'fr_CA': 'Visitez Google', // French
'es_MX': 'Visite Google' // Spanish
},
imgClasses: 'custom-image-class another-class',
imgAttributes: {
'data-image-attribute': 'image-attribute-value'
},
onclick: () => {
window.MyIntegration.activateModalOverlay();
}
});
return button;
})(window.DDC.APILoader);
The above example generates this markup:
<a href="https://www.google.com/" class="btn btn-primary mb-3" data-custom-attribute="attribute-value" target="_blank" style="border: 2px solid rgb(204, 0, 0);">
<img src="https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png" alt="Visit Google" class="custom-image-class another-class" data-image-attribute="image-attribute-value">
</a>
The create method can be used to generate markup which adheres to our standard practices. This allows you to simply "create a button", for example, without having to know the inner workings of how buttons should be created on different types of sites.
Currently only the "button" type is supported, however other types may be added in the future. Please let us know if there are particular types of elements you want to create so we can work to incorporate your feedback into this API.
The available options for the button
type are as follows:
Field Key | Example Value | Field Format | Purpose |
---|---|---|---|
href |
https://www.google.com/ |
String |
The link URL. Must begin with https:// . |
text |
See functional example. |
Object |
The localized button text object. |
classes |
btn btn-primary |
String |
Classes to place on the link element. |
style |
border: 2px solid #c00 |
String |
Inline styles to place on the link element. |
attributes |
See functional example. |
Object |
An object of key/value pairs to add to the link element as attributes. |
imgSrc |
https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png |
String |
The URL to the button image. |
imgAlt |
See functional example. |
Object |
The localized image alt text object. |
imgClasses |
custom-image-class another-class |
String |
Classes to place on the image. |
imgAttributes |
See functional example. |
Object |
An object of key/value pairs to add to the image element as attributes. |
onclick |
See functional example. |
Function |
The onclick handler to attach to the button. |
API.append(targetEl, appendEl)
Usage
(async APILoader => {
const API = await APILoader.create();
API.append(targetEl, appendEl);
})(window.DDC.APILoader);
For example, used in conjunction with the
insert
and thecreate
methods your code might look similar to this:
(async APILoader => {
const API = await APILoader.create();
API.insert('target-location-name', (elem, meta) => {
let lowPrice = Math.round(meta.finalPrice - 1000);
let highPrice = Math.round(meta.finalPrice + 1000);
const button = API.create('button', {
text: 'Search This Price Range',
href: '/new-inventory/index.htm?internetPrice=' + lowPrice.toString() + '-' + highPrice.toString(),
classes: 'btn btn-primary',
style: 'margin-top: 12px;',
attributes: {
'target': '_blank'
}
})
API.append(elem, button);
});
})(window.DDC.APILoader);
When calling the insert method, the goal is to insert some markup into a location on the page. Once you have constructed the element(s) you wish to insert, you may call the append
method to complete the process.
API.loadCSS(resourceUrl)
The loadCSS method is a simple way to include an additional CSS stylesheet file required for your integration. The method returns a JavaScript Promise that resolves when the loading is complete. You may then insert markup that depends on that styling to avoid displaying a flash of unstyled content.
Usage
(async APILoader => {
const API = await APILoader.create();
// Loads a CSS stylesheet
API.loadCSS('https://www.company.com/integration.css')
.then(() => {
// Code to execute after your stylesheet has loaded.
});
})(window.DDC.APILoader);
API.loadJS(resourceUrl, attributeMap)
The loadJS method is a simple way to include additional JavaScript files required for your integration. The method returns a JavaScript Promise which you can use to know when file loading is complete, to then run any necessary initialization functions, etc.
Usage
(async APILoader => {
const API = await APILoader.create();
// Loads a JavaScript file
API.loadJS('https://www.company.com/script.js')
.then(() => {
// Code to execute after your JavaScript file has loaded.
});
})(window.DDC.APILoader);
Usage with attributes
In some cases your code may look for data attributes on the inserted script tag. In this case, you may use the following example as a guide to add the necessary attributes:
(async APILoader => {
const API = await APILoader.create();
const attributeMap = new Map([
['data-license-key', 'abc123'],
['id', 'myCompanyScript']
]);
API.loadJS('https://www.company.com/script.js', attributeMap).then(() => {
// Code to execute after your JavaScript file has loaded.
});
})(window.DDC.APILoader);
Note that the
attributeMap
parameter is optional and may be ommitted in most cases.
Insert Locations
Insert locations are areas of pages where you can safely insert markup. These exist in commonly used areas, such as below vehicle pricing on search and detail pages. This section details the current available locations. Please let us know if there are other locations you want to target beyond those listed here.
See the insert documentation for more details on the insert method.
Vehicle Media
Usage:
(async APILoader => {
const API = await APILoader.create();
API.insert('vehicle-media', (elem, meta) => {
// This element is positioned below the vehicle image area on vehicle search pages.
});
})(window.DDC.APILoader);
Example Implementation:
(async APILoader => {
const API = await APILoader.create();
API.subscribe('page-load-v1', ev => {
if (ev.payload.searchPage) {
API.insert('vehicle-media', (elem, meta) => {
const button = API.create('button', {
text: 'Watch Video',
href: 'https://www.providerdomain.com/path/video-player.html?vin=' + meta.vin,
classes: 'btn btn-primary dialog',
style: 'margin-top: 12px;',
attributes: {
'target': '_blank'
}
});
API.append(elem, button);
});
}
});
})(window.DDC.APILoader);
This element is positioned below the vehicle image area on vehicle search pages.
Vehicle Badge
Usage:
(async APILoader => {
const API = await APILoader.create();
API.insert('vehicle-badge', (elem, meta) => {
// This element is positioned below the vehicle tech specs area on vehicle search and detail pages.
});
})(window.DDC.APILoader);
Example Implementation:
(async APILoader => {
const API = await APILoader.create();
API.subscribe('page-load-v1', ev => {
if (ev.payload.searchPage || ev.payload.detailPage) {
API.insert('vehicle-badge', (elem, meta) => {
if (meta.inventoryType !== 'used') {
return;
}
const img = document.createElement('img'),
a = document.createElement('a');
img.src = 'https://static.dealer.com/v8/global/images/franchise/white/logo-certified-carfax-free-lrg.png';
img.alt = 'Carfax Free Report';
a.href = 'https://www.carfax.com/VehicleHistory/p/Report.cfx?partner=DLR_3&vin=' + meta.vin;
a.target = '_blank';
a.innerHTML = img.outerHTML;
API.append(elem, a);
});
}
});
})(window.DDC.APILoader);
This element is positioned below the vehicle tech specs area on vehicle search and detail pages.
Vehicle Payments
Usage:
(async APILoader => {
const API = await APILoader.create();
const callback = (elem, meta) => {
// Insert your content here.
}
API.insert('vehicle-payments-primary', callback);
API.insert('vehicle-payments', callback);
})(window.DDC.APILoader);
Example Implementation:
(async APILoader => {
const API = await APILoader.create();
API.subscribe('page-load-v1', ev => {
const { searchPage, detailPage } = ev.payload;
const callback = (elem, meta) => {
const button = API.create('button', {
text: 'Vehicle Payments',
href: '#',
classes: 'btn btn-primary'
})
API.append(elem, button);
};
// Only execute the code on search results and vehicle details pages.
if (searchPage || detailPage) {
// This element exists only on the "Grid View" layout of the vehicle search page.
// The content appears below the primary price of the vehicle and above the "Info", "Specials" and "Pricing" tabs.
// Use this in addition to the `vehicle-payments` location if you want your content to be shown before the user clicks on the pricing tab.
if (searchPage) {
API.insert('vehicle-payments-primary', callback);
}
// This element is positioned directly below the vehicle pricing area on vehicle search and detail pages.
// On the "Grid View" layout of the vehicle search page, this content is placed inside the "Pricing" tab, below the vehicle price.
API.insert('vehicle-payments', callback);
}
});
})(window.DDC.APILoader);
This element is positioned directly below the vehicle pricing area on vehicle search and detail pages.
Vehicle Pricing
Usage:
(async APILoader => {
const API = await APILoader.create();
API.insert('vehicle-pricing', (elem, meta) => {
// This element is positioned after the vehicle-payments insert location, and is placed below the pricing/incentives area on vehicle search and detail pages.
});
})(window.DDC.APILoader);
Example Implementation:
(async APILoader => {
const API = await APILoader.create();
API.subscribe('page-load-v1', ev => {
// Only execute the code on search results and vehicle details pages.
if (ev.payload.searchPage || ev.payload.detailPage) {
API.insert('vehicle-pricing', (elem, meta) => {
let lowPrice = Math.round(meta.finalPrice - 1000);
let highPrice = Math.round(meta.finalPrice + 1000);
const button = API.create('button', {
text: 'Search This Price Range',
href: '/' + meta.inventoryType + '-inventory/index.htm?internetPrice=' + lowPrice.toString() + '-' + highPrice.toString(),
classes: 'btn btn-primary',
style: 'margin-top: 12px;'
})
API.append(elem, button);
});
}
});
})(window.DDC.APILoader);
This element is positioned after the vehicle-payments insert location, and is placed below the pricing/incentives area on vehicle search and detail pages.
Vehicle Media Container
Usage:
(async APILoader => {
const API = await APILoader.create();
API.insert('vehicle-media-container', (elem, meta) => {
// This element is the media gallery container on vehicle details pages.
// Injecting into this location will replace the media gallery with the elements you insert.
});
})(window.DDC.APILoader);
Example Implementation:
(async APILoader => {
const API = await APILoader.create();
API.subscribe('page-load-v1', ev => {
if (ev.payload.detailPage) {
API.insert('vehicle-media-container', (elem, meta) => {
const containerEl = document.createElement('div');
containerEl.style = 'background-color: #ff0000; font-size: 30px; width: 100%; height: 540px; margin: 0 auto; padding: 100px; text-align: center;';
containerEl.innerHTML = 'Your media container goes here.';
API.append(elem, containerEl);
});
}
});
})(window.DDC.APILoader);
This element is the media gallery container on vehicle details pages. Injecting into this location will replace the media gallery with the elements you insert.
Primary Banner
Usage:
(async APILoader => {
const API = await APILoader.create();
API.insert('primary-banner', (elem, meta) => {
// This element is typically positioned in a prominent location above the vehicle listings on the Search Results Page.
// On the Details page, it is near the top of the vehicle information, below the media gallery.
});
})(window.DDC.APILoader);
Example Implementation:
(async APILoader => {
const API = await APILoader.create();
API.subscribe('page-load-v1', ev => {
if (ev.payload.searchPage || ev.payload.detailPage) {
API.insert('primary-banner', (elem, meta) => {
const img = document.createElement('img'),
a = document.createElement('a');
img.src = 'https://pictures.dealer.com/d/ddcdemohonda/0217/15bd9bd8ecf0b2a292a91cecb08c595bx.jpg';
img.alt = 'New 2015 Honda Pilot';
img.title = 'New 2015 Honda Pilot';
a.href = '/specials/new.htm';
a.innerHTML = img.outerHTML;
API.append(elem, a);
});
}
});
})(window.DDC.APILoader);
This element is positioned in a prominent location above the vehicle listings on the Search Results Page.
On the Details page, it is positioned at the top of the vehicle information, below the media gallery.
You can target either the listings or details page by first subscribing to the page-load-v1
event, then using the event values of payload.searchPage
and payload.detailPage
to check the page type.
Secondary Content
Usage:
(async APILoader => {
const API = await APILoader.create();
API.insert('secondary-content', (elem, meta) => {
// This element is the a secondary content container on vehicle details pages roughly 2/3 of the way down.
// It may also be added custom to one or more standalone pages on the website.
});
})(window.DDC.APILoader);
Example Implementation:
(async APILoader => {
const API = await APILoader.create();
API.subscribe('page-load-v1', (ev) => {
if (!ev.payload.detailPage) {
return;
}
API.insert('secondary-content', (elem, meta) => {
const containerEl = document.createElement('div');
containerEl.style = 'background-color: #ff0000; font-size: 30px; width: 100%; height: 540px; margin: 0 auto; padding: 100px; text-align: center;';
containerEl.innerHTML = 'Your secondary content container goes here.';
API.append(elem, containerEl);
});
});
})(window.DDC.APILoader);
By default, this element is roughly 2/3 of the way down on vehicle details pages.
Because this may also be present on one or two standalone pages as custom additions, it is likely you will want to exclusively target Vehicle Details pages by first subscribing to the page-load-v1
event, then using the event value of payload.detailPage
to check the page type.
Content
Usage:
(async APILoader => {
const API = await APILoader.create();
API.insert('content', (elem, meta) => {
// This element is will only insert on pages created by us for your purposes.
// It may also be present on pages created for another integration.
});
})(window.DDC.APILoader);
Example Implementation:
(async APILoader => {
const API = await APILoader.create();
API.subscribe('page-load-v1', ev => {
if (ev.payload.pageName === 'YOUR_LANDING_PAGE') { // Note: Replace 'pageName' with the one we provide at page creation.
API.insert('content', (elem, meta) => {
const containerEl = document.createElement('div');
containerEl.classList = 'bg-neutral-950 text-light';
containerEl.style = 'font-size: 35px; width: 100%; height: 540px; margin: 0 auto; padding: 100px; text-align: center;';
containerEl.innerHTML = 'Your content container goes here.';
API.append(elem, containerEl);
});
}
});
})(window.DDC.APILoader);
This element will represent the entirety of the empty space between the header and footer on a custom landing page.
The example implementation can be tested here: https://www.roimotors.com/tools/your-landing-page.htm
You will need to subscribe to the page-load-v1
event, then use the event value of payload.pageName
to ensure you only target your dedicated landing page.
Once ready for production, you will need to work with us to ensure we provide a landing page with this target.
Utility Methods
In addition to the event based system for working with sites, some utility methods are available to directly access data needed to make decisions in your code. Rather than waiting for a particular event to fire, you can query the information directly at any point in your code to ensure it is in context at the point where you need it. All utility methods return a JavaScript Promise so you can ensure the data is available before proceeding with the rest of your code.
API.utils.getAttributeForVehicles(attribute)
Usage:
(async APILoader => {
const API = await APILoader.create();
API.subscribe('vehicle-data-updated-v1', data => {
API.log(data.payload.pageData); // Logs the Page Data object
API.log(data.payload.vehicleData); // Logs the updated Vehicle Data object
API.utils.getAttributeForVehicles('vin').then(vins => {
// With the updated list of VINs, you could query your service
// to determine which VINs are supported by your service before
// placing relevant content such as buttons or iframes.
// Code to query your service goes here.
// Output an array of VINs for vehicles currently displayed on the page.
API.log(vins);
});
})
})(window.DDC.APILoader);
This can be used to obtain an array of attributes for the currently displayed vehicles on a page. For example, passing the attribute 'finalPrice' would return an array of all of the final prices on the page. Passing 'vin' would return an array of vehicle vins. You can pass any of the attributes that are present on the Vehicle Event object and have this return an array.
API.utils.getConfig(testConfig)
Usage:
(async APILoader => {
const API = await APILoader.create();
const testConfig = {
dealerId: "12345",
showOnSRP: true,
showOnVDP: false,
apiKey: "abcd12349876zyxw"
}
API.utils.getConfig(testConfig).then(config => {
// Output the configuration object for your integration (if defined).
API.log(config);
});
})(window.DDC.APILoader);
This fetches a JavaScript object of your integration's configuration for the current website and page. Not all integrations have configuration options in our system (aside from enabled/disabled), but if your integration does, you can use this to obtain the configuration data.
testConfig
is an optional parameter you can send into the getConfig
method. The format should be a JavaScript object. Using the testConfig
, you can specify any key/value pairs you need for configuration data for your integration to test various scenarios. The testConfig
is only applied when ?_integrationMode=debug
is in the URL of the site you are testing, and that site does not already have your integration configured. If the site does have your integration configured, you can override the config specified on our side by also including &_integrationConfig=override
in the URL.
API.utils.getDealerData()
Usage:
(async APILoader => {
const API = await APILoader.create();
API.utils.getDealerData().then(dealerData => {
// Logs the Dealership Info Event object for the current website.
API.log(dealerData);
});
})(window.DDC.APILoader);
This fetches the Dealership Info Event object for the current website.
API.utils.getJwtForSite()
Usage:
(async APILoader => {
const API = await APILoader.create();
API.utils.getJwtForSite().then(jwtObject => {
API.log(jwtObject);
// Returns a data structure like this:
// {
// jwt: "eyJraWQiOiIya0k0XzIyZoLUUi...KpSUf6vJ8b9Z1NDRcIgv0GrZoiqPhTunw" // String
// }
});
})(window.DDC.APILoader);
This fetches an object containing a Java Web Token which can be used to secure/verify the request from our site to your service. This tool offers the capability for you to validate that the request for content from your service originated from our platform and enables you to determine whether or not the content should be served. For more details regarding how to use this with your service, please refer to the JWT usage documentation.
API.utils.getJwtForVehicles()
Usage:
(async APILoader => {
const API = await APILoader.create();
API.utils.getJwtForVehicles().then(jwtObject => {
API.log(jwtObject);
// Returns a data structure like this:
// {
// vins: ["1HGCV1F51LA013850", "1HGCV1F16LA029720", "1HGCV1F32LA011829"], // Array
// jwt: "eyJraWQiOiIya0k0XzIyZoLUUi...KpSUf6vJ8b9Z1NDRcIgv0GrZoiqPhTunw" // String
// }
});
})(window.DDC.APILoader);
This fetches an object containing the array of VINs on the current page and a corresponding Java Web Token which can be used to secure/verify the request from our site to your service. This tool offers the capability for you to validate that the request for content from your service originated from our platform and enables you to determine whether or not the content should be served. For more details regarding how to use this with your service, please refer to the JWT usage documentation.
API.utils.getPageData()
Usage:
(async APILoader => {
const API = await APILoader.create();
API.utils.getPageData().then(pageData => {
// Outputs the Page Data Object for the current page.
API.log(pageData);
});
})(window.DDC.APILoader);
API.utils.getPixallVisitorId()
The getPixallVisitorId method returns the Pixall ID for the current website visitor. Pixall provides a unique identifier for each visitor to aggregate analytics events initiated by that shopper across marketplaces.
Usage
(async APILoader => {
const API = await APILoader.create();
const visitorId = await API.utils.getPixallVisitorId();
// `visitorId` now equals a string value similar to this example: 'e9qAVkIRhnC64Pd4EfdwhB3Z'
})(window.DDC.APILoader);
This fetches the Page Event object for the current website and page.
API.utils.getUnlockedVehicles()
The utility method getUnlockedVehicles
returns an array of vehicle UUIDs where the vehicle's pricing has already been unlocked.
This can be useful when paired with the unlockPricing
method. When vehicle-data-updated-v1
triggers, a list of currently displayed vehicles is provided. You could gather the list of vehicles, skip the ones which are already unlocked, and then call your service for a smaller subset of vehicles which may need to be unlocked.
Usage:
(async APILoader => {
const API = await APILoader.create();
API.subscribe('vehicle-data-updated-v1', data => {
const unlockedUuids = await API.utils.getUnlockedVehicles();
});
})(window.DDC.APILoader);
Another example:
(async APILoader => {
const API = await APILoader.create();
API.subscribe('vehicle-data-updated-v1', data => {
// Get the list of unlocked vehicles
const unlockedUuids = await API.utils.getUnlockedVehicles();
// Get the list of all vehicles
const uuids = API.utils.getAttributeForVehicles('uuid');
// Filter the unlocked UUIDs from the UUIDs.
const finalUuids = uuids.filter((el) => !unlockedUuids.includes(el));
// Call your service with the list of finalUuids here, to reduce network overhead.
// Note: `callToYourService` is just an example here and not an implemented function in the API.
const uuidsToUnlock = await callToYourService(finalUuids);
// Unlock vehicles which are not already unlocked, and your service indicates should be unlocked.
API.utils.unlockPricing(uuidsToUnlock);
});
})(window.DDC.APILoader);
API.utils.getUrlParams()
Usage:
(async APILoader => {
const API = await APILoader.create();
const urlParams = API.utils.getUrlParams(); // Returns the current URL parameters as object attributes, so you can easily access the values.
API.log(urlParams); // Log the entire object.
API.log(urlParams.query); // Access just the `query` parameter, for example.
})(window.DDC.APILoader);
NOTE: This method returns synchronously, not as a Promise, because the URL Params are already available and do not require asynchronous behavior for performance.
Running this function at a URL such as this:
https://www.roimotors.com/?query=This%20is%20the%20query&hello=world&foo=bar
Will return the following object:
{
query: "This is the query",
hello: "world",
foo: "bar"
}
API.utils.getVehicleData()
Usage:
(async APILoader => {
const API = await APILoader.create();
const config = API.utils.getVehicleData().then(vehicleData => {
// Outputs the current set of vehicle data.
API.log(vehicleData);
});
})(window.DDC.APILoader);
API.utils.unlockPricing(uuids)
The utility method unlockPricing
is used to unlock special pricing for specific vehicles on a page.
You can provide the vehicles to unlock by passing an array of vehicle UUIDs to the method. If you would like to unlock all vehicles on the page, omit the uuids
parameter from the function call.
The method must be called on each page where you want the vehicles to be unlocked. If a vehicle has been previously unlocked, it will be initially displayed unlocked on a subsequent view of the vehicle. However, when faceting and searching vehicles it is common for a mix of locked and unlocked vehicles to be rendered. Therefore, it is necessary to call the function each time the vehicle-data-updated-v1
event is triggered.
Usage:
(async APILoader => {
const API = await APILoader.create();
API.subscribe('vehicle-data-updated-v1', data => {
// You could call a service here to determine the list of vehicles to unlock based on the set
// of vehicles presented on the current view, then construct the array of uuids accordingly.
const uuids = [
'f4b436e10a0e09a844d99ec8c92cf29c',
'1db75afc0a0e09713efa52d69381e2f1',
'808bb6a00a0e09716fa39a4a8b079353'
];
API.utils.unlockPricing(uuids);
});
})(window.DDC.APILoader);
JWT Usage Documentation
This API provides a method for loosely coupled content delivery authentication, enabling you to only provide content for valid requests from Dealer.com sites.
We have a sample application to demonstrate how to consume Dealer.com's Integrated Partner Program JWKs, JWTs, and JWSs. How to pronounce JWSs
is left as an exercise for the reader.
JWTs
JWTs are used to claim that, at the time the token is issued, Dealer.com rendered a webpage containing specific vehicles, on a specific domain, for a specific account.
An off-platform vendor can validate this token and use that result to decide whether or not to provide paid content.
This claim is then signed with a rotating RSA private key, and the public keys are published on a *.dealer.com
domain with HTTPS.
Successful validation of a JWT with one of our public keys ensures that the claims were not tampered with.
The consumer of a JWT is responsible for validating the signature, and can make their own decision about whether or not to honor the token. Dealer.com provides an iat
(Issued At) claim, but does not provide an exp
(Expiration Time) claim. This means the consumer is free to (and should) define their own limits on token freshness. An implicit expiry is reached when Dealer.com is no longer publishing the public key of the keypair that a particular JWT is signed with.
JWKs
JWKs are published on a Dealer.com domain with HTTPS, which prevents a malicious actor from distributing public keys that would validate JWT signatures not generated by Dealer.com.
JWK public keys are guaranteed to be published well before the corresponding private key is used for generating JWTs.
This means consumers polling for public keys will always be able to validate the signature on a new, valid JWT.
JWKs are hosted on Dealer.com's Content Delivery Network, and therefore are highly available to consumers.
Consumers SHOULD poll and cache JWKs for a short period of time. Consumers SHOULD NOT cache or store keys for an extended period of time to ensure invalidated/expired keys are removed in a timely manner.
JWKs are published as a JWK Set, and each JWT signature contains the kid
(Key Id) of the JWK used to verify the signature.
Details
The details in this document are not necessarily final and are subject to change with notice.
At the time of writing:
JWK URL: https://api.web.dealer.com/ipp/keys/jwks.json
JWK Algorith: 2048-bit RSA
JWK Usage:
Private Key: Used to sign JWTs for up to 1 day
Public Key: Published 1 day before Private Key is used, removed from publishing after 30 days
JWK Consumer Cache Duration: 0-15 minutes, recommend 1 minute.
JWA: RS256
JWT Claims:
iss
: (Issuer) Dealer.comaud
: (Audience) IPPsub
: (Subject) The Dealer.com account IDdomain
: The public hostname associated with the Dealer.com account IDVINs
: A list of VINs that Dealer.com believes the given subject has access to
Example
The provided example is a spring-boot application that polls the JWK url to fetch public keys as they're published. It has a single endpoint (/jwt/validate/{jwt}
) that accepts a JWT as a path parameter and validates the signature and audience.
Retrieving a JWT
A test JWT can be generated for any dealer.com site by calling <domain>/api/ipp/jwt/vehicles?vins=
and providing a list of VINs that are valid for that site.
A full example for
roimotors.com
would be:
curl -Lv https://roimotors.com/api/ipp/jwt/vehicles?vins=1HGCV1F46LA013527,1HGCV1F51LA013850,1HGCV1F52LA011170,1HGCV1F57LA003078
Decode the token at https://jwt.io/ to check that all of the requested VINs were returned. If any VINs are missing, that means that Dealer.com believes the given account doesn't have access to display that inventory.
The Web Integration API provides a convenience method for obtaining the valid VINs and JWT for the current page where your code is executing. This accomplishes the step above and provides you with the JWT which can be passed to your system through a URL parameter on a request which then can be validated on your service with the following validation step.
Validating the JWT
Once you have a valid JWT, you can validate it with the sample application. The JWT is guaranteed to be url-safe.
curl -v localhost:8080/jwt/validate/<jwt>
Debugging / Troubleshooting
Example Implementation:
(async APILoader => {
const API = await APILoader.create();
API.subscribe('page-load-v1', ev => {
if (ev.payload.searchPage) {
API.log('My integration has loaded and this is a search results page.');
}
});
})(window.DDC.APILoader);
When developing an integration, it's helpful to know if the API is doing what you expect and if your code is running successfully. The API intentionally suppresses most errors in regular use cases, however you can opt to view the error messages and API events by adding the following URL parameter to any page of any Dealer.com web site:
?_integrationMode=debug
This will allow the API to output some log messages to your browser's console regarding the actions it is taking.
For more verbose logging, use this combination of URL parameters:
?_integrationMode=debug&_integrationLogLevel=trace
This will output more information about the actions being taken in your script to the browser console so you can more easily see what actions are happening. You can easily filter the browser console to show only these messages by typing Web Integration API
into the filter
field in your browser.
Additionally, you can instrument your own integration with log messages to validate that the expected code is being executed and desired code paths are being followed.
To log messages from within your integration code, simply use the API.log
method the same way you would use console.log
. Those messages will be output to your console when the above URL parameter is present and will be suppressed when it is not.
Sample Code
The sample code here is provided as a starting point for how to accomplish tasks that are related to integrations but that may not fit squarely in the responsibility of the Integration API. Code may need to be modified to fit your integration's use case.
Resizing an iframe
Based on Content Changes
iframe Code:
document.body.style.overflow = 'hidden';
let sendResizeMessage = () => {
window.parent.postMessage({
type: 'IFRAME_HEIGHT_RESIZE',
target: 'test-integration-iframe', // Note: Replace 'test-integration-frame' with your actual iframe identifier.
frameHeight: document.body.offsetHeight + 10 /* a little extra for good measure */
}, '*');
}
if (window.ResizeObserver) {
const heightObserver = new ResizeObserver(() => {
sendResizeMessage();
});
heightObserver.observe(document.body);
}
Integration Code:
(async APILoader => {
const API = await APILoader.create();
API.insert('content', (elem, meta) => {
const iframeElem = document.createElement('iframe');
iframeElem.src = 'https://www.yourdomain.com/path-to-iframe.htm';
iframeElem.classList.add('test-integration-iframe'); // Note: Replace 'test-integration-frame' with your actual iframe identifier.
API.append(elem, iframeElem);
});
let setIframeHeight = e => {
if (e.origin !== 'https://www.yourdomain.com') {
// You should ALWAYS verify the origin matches the third party domain
// the iframe is loaded from. For more information, see:
// https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#Security_concerns
return;
}
if (e.data.type === 'IFRAME_HEIGHT_RESIZE' && e.data.frameHeight && e.data.target) {
const iframes = document.getElementsByClassName(e.data.target);
if (iframes.length === 1) {
iframes[0].style.height = e.data.frameHeight + 'px';
}
}
}
window.addEventListener('message', setIframeHeight, false);
})(window.DDC.APILoader);
An integration may want to insert an iframe that resizes as its contents change. One possible way to accomplish this is for the iframe and the integration to work together as shown in the sample code from the pane on the right side of this page. You can see the sample code running here.
IFrame Responsibilities:
- Determine when content dimensions change - One way to do this is using
ResizeObserver
. - Communicate the new dimension to the outer page - iframes can communicate with the parent page using
postMessage
. - Ensure a scrollbar never shows up within the iframe - Styling may be used to ensure a scrollbar never appears in the iframe.
Integration Responsibilities(on the outer page):
- Insert the iframe into the page - You can use the API methods to insert an iframe into a location.
- Listen for resize messages and resize the iframe
Considerations
- If you use
postMessage
, ensure that you check the event's origin to alleviate security concerns. - The integration resizing code supports multiple iframes from the same vendor. You may need to modify the code to target your iframes differently.