Appearance
Complete Custom Integration Example
This is a complete walkthrough of the custom integration path — creating the entity hierarchy as a partner, then rendering offers yourself by calling OData directly and wiring up the click and impression URLs returned in the response. If you have full control over the surface and don't need to customize rendering, prefer the Embedded Web SDK — it handles fetching, rendering, impression tracking, and click handling for you. This guide is for cases where you're rendering offers in your own UI.
1. Create the entity hierarchy
bash
#!/bin/bash
BASE_URL="https://pr-api.falconlabs.us"
PLATFORM_TOKEN="plat_1234567890abcdef1234567890abcdef"
# Step 1: Create Publisher
echo "Step 1: Creating publisher..."
PUBLISHER_RESPONSE=$(curl -s -X POST "${BASE_URL}/api/v1/publishers" \
-H "Authorization: Bearer${PLATFORM_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "My Fashion Store",
"contactName": "John Doe",
"contactEmail": "john@myfashionstore.com",
"contactPhone": "+1-555-0123"
}')
# Extract keys
PUBLIC_KEY=$(echo $PUBLISHER_RESPONSE | jq -r '.data.publicKeys[0]')
PRIVATE_KEY=$(echo $PUBLISHER_RESPONSE | jq -r '.data.privateKeys[0].bearer')
echo "Public Key:$PUBLIC_KEY"
echo "Private Key:$PRIVATE_KEY"
# Step 2: Create Site
echo ""
echo "Step 2: Creating site..."
SITE_RESPONSE=$(curl -s -X POST "${BASE_URL}/api/site" \
-H "Authorization: Bearer${PRIVATE_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "My Fashion Store",
"domain": "myfashionstore.com",
"status": "active"
}')
SITE_ID=$(echo $SITE_RESPONSE | jq -r '.data.id')
echo "Site ID:$SITE_ID"
# Step 3: Create Placement (defaults to test mode)
echo ""
echo "Step 3: Creating placement..."
PLACEMENT_RESPONSE=$(curl -s -X POST "${BASE_URL}/api/placements" \
-H "Authorization: Bearer${PRIVATE_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "Thank You Page Ad",
"siteId": "'${SITE_ID}'",
"pageType": "THANK_YOU_PAGE",
"isLiveMode": false
}')
PLACEMENT_ID=$(echo $PLACEMENT_RESPONSE | jq -r '.data.id')
echo "Placement ID:$PLACEMENT_ID"Test mode reminder: Step 3 omits
isLiveMode(defaulting tofalse), which puts the placement in test mode — OData will return mock offers, not real ones from the network. Production publishers that should serve real offers must passisLiveMode: true. See the Placements API for details.
2. Fetch offers via OData
bash
echo ""
echo "Step 4: Fetching offers..."
curl -s -X GET "${BASE_URL}/api/odata?placementId=${PLACEMENT_ID}&sessionId=test_session_123&count=4&at.email=customer@example.com&at.orderid=ORDER-12345&at.clientIp=203.0.113.42&at.userAgent=Mozilla%2F5.0" \
-H "Authorization: Bearer${PUBLIC_KEY}" | jq '.'
echo ""
echo "Integration complete!"
echo "Public Key:$PUBLIC_KEY"
echo "Private Key:$PRIVATE_KEY"
echo "Placement ID:$PLACEMENT_ID"Each offer in the response includes three URLs you'll wire up in the next step:
clickUrl— navigate the customer here when they click the offerbeaconUrl— call this when the offer becomes visible (impression)closeUrl— call this when the customer dismisses the offer
Backend proxy reminder: If your backend is calling OData on behalf of the browser, pass the customer's real IP and User-Agent via
at.clientIp(read fromX-Forwarded-For, typically the first entry) andat.userAgent(read from the inboundUser-Agentheader). Without these, every request looks like it's coming from your server and geo/device targeting breaks for all users.
See the OData API docs for the full response shape and parameter reference.
3. Render offers and wire up impression + click tracking
Once you have the offer payload, render each offer in your UI and:
- Fire the impression beacon (
beaconUrl) the first time the offer becomes visible to the customer. - Use the click URL (
clickUrl) as the navigation target when the customer clicks the offer's CTA — that endpoint records the click and redirects to the advertiser.
A minimal browser-side example (framework-agnostic):
js
// `offers` is the `offers` array from the OData response.
function renderOffer(offer, container) {
const card = document.createElement("a");
card.href = offer.clickUrl; // wraps the offer in the click URL
card.rel = "noopener";
card.innerHTML = `
<h3>${offer.title}</h3>
<p>${offer.description}</p>
<button>${offer.ctaText}</button>
`;
// Fire the impression beacon the first time the card is on screen.
const observer = new IntersectionObserver((entries, obs) => {
for (const entry of entries) {
if (entry.isIntersecting) {
navigator.sendBeacon(offer.beaconUrl); // impression
obs.disconnect();
}
}
}, { threshold: 0.5 });
observer.observe(card);
container.appendChild(card);
}Important: Skipping the impression beacon means Falcon never records that the offer was viewed, which breaks attribution and reporting. Skipping the click URL means clicks aren't recorded and the customer won't be redirected to the advertiser. Both are required for a custom integration to be considered complete.
For the full event semantics — including the JSON variants and required parameters — see the Impression API and Click API docs.