Skip to content

Overlay Web Integration ​

Overview ​

The Falcon SDK overlay mode renders a modal dialog over your page with a semi-transparent dark backdrop. Users see a centered card (max-width ~460-560px) with promotional offers (Perks) and can interact with them or close the modal.

The modal can also be minimized to a floating button — a small persistent element (bottom-right on desktop, full-width bar on mobile) that lets users re-open the offers later.

Typical use case: show offers on the order confirmation page after a purchase is complete.

Quick Start ​

Add this to your order confirmation page (or any page where you want to show offers). The modal will appear automatically when the page loads:

html
<head>
  <script src="https://d6y5cd3imay52.cloudfront.net/sdk/v1/falcon-sdk.js"></script>
  <script>
    (async function () {
      try {
        // 1. Initialize SDK
        await FalconSDK.init("YOUR_SDK_KEY");

        // 2. Create a placement instance with user/order data
        const perks = FalconSDK.createPerksInstance("YOUR_PLACEMENT_ID", {
          attributes: {
            orderId: "ORDER-123",
            hashedEmail: "SHA256_HEX_OF_EMAIL_LOWERCASE",
            firstname: "John",
            amount: "99.99",
          },
        });

        // 3. Load offers and show immediately if available
        const result = await perks.loadPerks();
        if (result.isReady) {
          perks.show();
        }
      } catch (error) {
        console.error("Falcon SDK error:", error);
      }
    })();
  </script>
</head>

That's it. The modal will appear with available offers.

Get your credentials: Contact your Falcon Labs account manager to obtain your SDK key and placement ID.

Staging: Point the script tag at https://d6y5cd3imay52.cloudfront.net/sdk/staging/falcon-sdk.js and use your staging SDK key while testing. See Staging Environment for the full URL reference across the Web SDKs.

Legacy link: Existing integrations using https://falconlabs.s3.us-east-2.amazonaws.com/sdk/falcon-sdk.js don't need to migrate, that link is kept up to date in parallel, it just isn't served through CloudFront and has no staging equivalent. New integrations should use the CloudFront URL above.

Framework Examples ​

React ​

Types for the SDK (add once, e.g. in falcon-sdk.d.ts):

tsx
declare const FalconSDK: {
  init(apiKey: string): Promise<void>;
  createPerksInstance(
    placementId: string,
    options?: { attributes?: Record<string, string | undefined> },
  ): PerksInstance;
};

interface PerksInstance {
  loadPerks(): Promise<{ isReady: boolean }>;
  show(options?: { title?: string; subtitle?: string }): void;
  hide(): void;
  minimize(): void;
  expand(): void;
  destroy(): void;
  addClickCallback(
    cb: (data: { index: number; offer: unknown }) => void,
  ): () => void;
  addCloseCallback(cb: () => void): () => void;
  addUIStateCallback(
    cb: (state: "hidden" | "shown" | "minimized") => void,
  ): () => void;
}

Auto-show on page load ​

Show the modal immediately when the component mounts (e.g. order confirmation page):

tsx
// Load the SDK script once in your index.html <head>:
// <script src="https://d6y5cd3imay52.cloudfront.net/sdk/v1/falcon-sdk.js"></script>

import { useEffect, useRef } from "react";

export function FalconPerksAutoShow({
  sdkKey,
  placementId,
  userHashedEmail,
  userName,
  orderId,
  amount,
}) {
  const instanceRef = useRef<PerksInstance | null>(null);

  useEffect(() => {
    async function initPerks() {
      try {
        // Already initialized by an earlier run of this effect. The key cannot
        // be swapped after the first call, so ignoring this is safe.
        await FalconSDK.init(sdkKey).catch(() => {});

        const perks = FalconSDK.createPerksInstance(placementId, {
          attributes: {
            orderId,
            hashedEmail: userHashedEmail,
            firstname: userName,
            amount,
          },
        });

        instanceRef.current = perks;

        const result = await perks.loadPerks();

        // A later run may already have replaced this instance.
        if (instanceRef.current !== perks) return;

        if (result.isReady) {
          perks.show({ subtitle: `Thanks, ${userName}!` });
        }
      } catch (error) {
        console.error("Falcon SDK error:", error);
      }
    }

    initPerks();

    return () => {
      instanceRef.current?.destroy();
      instanceRef.current = null;
    };
  }, [sdkKey, placementId, userHashedEmail, userName, orderId, amount]);

  return null;
}

Show on button click ​

Initialize the SDK once, then load and show perks each time the user clicks:

tsx
import { useEffect, useRef, useCallback } from "react";

export function FalconPerksButton({
  sdkKey,
  placementId,
  userHashedEmail,
  userName,
  orderId,
  amount,
}) {
  const instanceRef = useRef<PerksInstance | null>(null);

  useEffect(() => {
    async function initSDK() {
      try {
        // Already initialized by an earlier run of this effect. The key cannot
        // be swapped after the first call, so ignoring this is safe.
        await FalconSDK.init(sdkKey).catch(() => {});

        instanceRef.current = FalconSDK.createPerksInstance(placementId, {
          attributes: {
            orderId,
            hashedEmail: userHashedEmail,
            firstname: userName,
            amount,
          },
        });
      } catch (error) {
        console.error("Falcon SDK error:", error);
      }
    }

    initSDK();

    return () => {
      instanceRef.current?.destroy();
      instanceRef.current = null;
    };
  }, [sdkKey, placementId, userHashedEmail, userName, orderId, amount]);

  const handleShowPerks = useCallback(async () => {
    const perks = instanceRef.current;
    if (!perks) return;

    try {
      const result = await perks.loadPerks();

      // Destroyed while the offers were loading.
      if (instanceRef.current !== perks) return;

      if (result.isReady) {
        perks.show({ subtitle: `Hey, ${userName}!` });
      }
    } catch (error) {
      console.error("Falcon SDK error:", error);
    }
  }, [userName]);

  return <button onClick={handleShowPerks}>View Offers</button>;
}

Next.js ​

The React examples above work in Next.js. The only difference is loading the SDK script — use next/script instead of a <script> tag in index.html:

tsx
import Script from "next/script";

<Script
  src="https://d6y5cd3imay52.cloudfront.net/sdk/v1/falcon-sdk.js"
  strategy="afterInteractive"
/>;

Make sure to add "use client" to components that use the SDK.

Custom Attributes ​

Pass user and order data via attributes for revenue attribution, offer targeting, personalization, and rendering hints. All values are strings, except lineItems, which is JSON.

Keys are case-sensitive. Spell each one exactly as the table shows. Most are lowercase, but orderId, lineItems, hashedEmail and hashedPhone are camelCase. A key with the wrong casing is ignored rather than corrected, so orderid or order_id silently sends nothing. The SDK logs a console warning naming the correct spelling when it recognises the mistake.

AttributePriorityDescriptionFormat
orderIdRequiredUnique order/transaction identifier. Links the offer impression back to a specific purchase for revenue attributionString, no special characters (#, @, ., spaces)
hashedEmailRequired*Customer email, hashed on your end before it's passed inSHA-256 hex, lowercase, 64 characters
emailRequired*Customer email, plain text. The SDK hashes it in the browser (same SHA-256 algorithm) before anything is sent — nothing leaves the page in the clearValid email string
categoryOptionalOrder or product categoryString
subcategoryOptionalOrder or product subcategoryString
amountRecommendedOrder totalNumeric string (e.g. "99.99")
currencyOptionalCurrency codeISO 4217 (e.g. "USD", "EUR")
countryOptionalCustomer countryISO 3166-1 alpha-2 (e.g. "US", "GB")
languageOptionalCustomer languageISO 639-1 (e.g. "en", "fr")
firstnameRecommendedCustomer first nameString
lastnameRecommendedCustomer last nameString
hashedPhoneOptionalCustomer phone, hashed on your end before it's passed inSHA-256 hex, lowercase, 64 characters
mobileOptionalCustomer phone, plain text. Hashed in the browser the same way as emailString
ageOptionalCustomer ageNumeric string
genderOptionalCustomer genderString
billingzipcodeOptionalBilling ZIP or postal codeString
billingaddress1OptionalBilling address line 1String
billingaddress2OptionalBilling address line 2String
lineItemsOptionalContents of the shopper's cart or order, one entry per line item, in your own shape (for example sku, title, quantity, price). Used for product-aware offer targeting and rankingJSON array or object. Pass the value itself, the SDK serialises it; a pre-serialised JSON string is accepted too
paymenttypeOptionalPayment method (e.g. "credit_card", "paypal")String
ccbinOptionalFirst 6 digits of the credit card number (BIN)String
confirmationrefOptionalMerchant/seller name, or your own internal confirmation reference if separate from orderId. Displayed in the offer headline on some templatesString
themeOptionalColour scheme of the page the widget sits on. Pass "dark" when your page uses a dark background so the widget renders its dark palette. Any other value, or omitting it, renders the default light widget"dark" or "light"
darkBgColorOptionalBackground colour for the widget's dark palette, so the card matches your page. Hex only, three or six digits, with or without the leading hash. Ignored with theme: "light", and ignored when the colour is too light for the widget's white text, in which case the default dark background is used"#1B2A4A" or "1b2a4a"

* Pass either hashedEmail or email, not both. If both are present, hashedEmail takes priority. Without one of them (or orderId), the SDK still loads and displays offers, but there's nothing to match the impression back to the order, so attribution won't work — this isn't enforced as a hard error, so it's easy to miss during testing.

Hashed fields: hashedEmail and hashedPhone use trim → lowercase → SHA-256. Uppercase hex or un-trimmed input will not validate and gets silently dropped rather than erroring, so double check the format before sending.

Not attributes: IP address and User-Agent are read automatically from the browser request. State and city are resolved server-side from the visitor's IP, there's no attribute for either. country above is only needed if you want to override what's detected (for example, billing country differs from shipping country).

tsx
// Any JSON value. Objects and arrays are serialised by the SDK before sending.
type JsonValue =
  | string
  | number
  | boolean
  | null
  | JsonValue[]
  | { [key: string]: JsonValue };

interface CustomAttributes {
  // Unique order or transaction identifier.
  // Links the offer impression to a specific purchase.
  orderId?: string;

  // Customer email, hashed (SHA-256, lowercase) on your end.
  // Pass this or `email`, not both.
  hashedEmail?: string;

  // Customer email address, plain text.
  // The SDK hashes it in the browser before sending. Pass this or
  // `hashedEmail`, not both.
  email?: string;

  // Order or product category.
  category?: string;

  // Order or product subcategory.
  subcategory?: string;

  // Colour scheme of your page. "dark" renders the widget's dark palette.
  theme?: "dark" | "light";

  // Background for the dark palette, hex only. Too light a colour is ignored.
  darkBgColor?: string;

  // Customer first name.
  firstname?: string;

  // Customer last name.
  lastname?: string;

  // Total transaction amount as a string (e.g. "99.99").
  // Used for offer eligibility rules based on order value.
  amount?: string;

  // ISO country code (e.g. "US", "GB", "DE").
  // Determines which offers are available in the user's region.
  country?: string;

  // ISO language code (e.g. "en", "fr", "de").
  // Used to serve offers in the user's language.
  language?: string;

  // ISO currency code (e.g. "USD", "EUR", "GBP").
  currency?: string;

  // Seller or merchant name (e.g. "BestBuyCanada").
  // Displayed in the modal headline: "Your offers from {confirmationref}".
  // Useful when a store has multiple sellers.
  confirmationref?: string;

  // Billing ZIP or postal code.
  billingzipcode?: string;

  // Payment method type (e.g. "credit_card", "paypal").
  paymenttype?: string;

  // First 6 digits of the credit card number (BIN).
  // Used for card-based offer targeting.
  ccbin?: string;

  // Customer phone, hashed (SHA-256, lowercase) on your end.
  // Pass this or `mobile`, not both.
  hashedPhone?: string;

  // Customer mobile phone number, plain text.
  // Hashed in the browser the same way as `email`.
  mobile?: string;

  // Billing address line 1.
  billingaddress1?: string;

  // Billing address line 2.
  billingaddress2?: string;

  // Customer age as a string.
  age?: string;

  // Customer gender.
  gender?: string;

  // Cart or order line items as JSON, an array or object in your own shape.
  // Used for product-based offer targeting.
  lineItems?: JsonValue;
}

Show Options ​

Customize the modal header when calling show():

jsx
perks.show({
  title: "Exclusive Offers", // Custom modal title
  subtitle: "Thanks, John!", // Personalized subtitle
});

Both fields are optional.

Callbacks ​

loadPerks ​

Loads offers for the placement. Returns a promise with { isReady: boolean }. Must be called before show() — and again after each hide() cycle.

jsx
const result = await perks.loadPerks();
if (result.isReady) {
  perks.show();
}

addClickCallback ​

Fires when a user clicks an offer. Use for analytics or custom redirect logic.

jsx
perks.addClickCallback((data) => {
  console.log("Clicked offer index:", data.index);
  console.log("Offer data:", data.offer);
});

addCloseCallback ​

Fires when the modal is closed (X button, backdrop click, etc.).

jsx
perks.addCloseCallback(() => {
  console.log("Modal closed");
});

addUIStateCallback ​

Fires when the modal transitions between states: 'hidden', 'shown', or 'minimized'. Useful for syncing your own UI (e.g. dimming page content while the modal is open).

jsx
perks.addUIStateCallback((state) => {
  console.log("UI state:", state); // 'hidden' | 'shown' | 'minimized'
});

Error Handling ​

The SDK throws on invalid operations. Common errors you may encounter:

ErrorCause
SDK not initializedFalconSDK.init() was not called before creating instances
Placement "{id}" already in useAnother instance with the same placement ID exists. Call destroy() first
Perks not readyshow() was called before loadPerks() completed or after TTL expired
Perks already shownshow() or loadPerks() was called while the modal is visible
Perks loadingloadPerks() was called while a previous load is still in progress

Best Practices ​

  1. Initialize once — call FalconSDK.init() a single time when your page loads, not on every interaction
  2. Pass attributes — the more user/order data you provide, the more relevant the offers will be
  3. Check isReady — always check result.isReady before calling show(), as there may not be offers available for this user
  4. Clean up in SPAs — call destroy() when navigating away from the page to avoid "Placement already in use" errors
  5. One placement = one instance — don't create multiple instances with the same placement ID

Troubleshooting ​

Modal doesn't appear:

  • Check the browser console for errors
  • Make sure loadPerks() returned { isReady: true } — if false, there are no offers for this user/placement
  • Verify your SDK key and placement ID with your account manager

loadPerks() returns { isReady: false }:

  • This is normal — it means no offers matched this user. No action needed

Script doesn't load:

  • Check for ad blockers or CSP rules blocking d6y5cd3imay52.cloudfront.net (or falconlabs.s3.us-east-2.amazonaws.com if you're still on the legacy link)

Need help? Contact your Falcon Labs account manager.