SDK Documentation

Pixel Installation

Install the Adverfly tracking pixel on your website

The Adverfly Pixel is a JavaScript snippet that tracks user interactions on your website. It captures pageviews, events, and conversions to power your analytics.

Quick Start

Add the following code to the <head> section of your website:

Request
<script>
  window.adverfly = window.adverfly || [];
  function advPxl() {
    var args = [false];
    for (var i = 0; i < arguments.length; i++) {
      args.push(arguments[i]);
    }
    adverfly.push(args);
  }
  advPxl("init", YOUR_WORKSPACE_ID);
  window.adverfly.store_currency = "EUR";
  window.adverfly.store_timezone = "Europe/Berlin";

  var script = document.createElement("script");
  script.type = "text/javascript";
  script.async = true;
  script.src = "https://sos-de-fra-1.exo.io/adv/advv2.01.js";
  document.getElementsByTagName("head")[0].appendChild(script);
</script>

Replace YOUR_WORKSPACE_ID with your Workspace ID from the Adverfly dashboard.

Configuration

ParameterTypeRequiredDescription
workspace_idnumberRequiredYour Adverfly Workspace ID
store_currencystringRequiredYour store's base currency (e.g., EUR, USD)
store_timezonestringRequiredYour store's timezone (e.g., Europe/Berlin)

Currency Conversion

The store_currency setting is important for accurate revenue tracking. If a transaction comes in with a different currency (e.g., a customer pays in USD), Adverfly automatically converts it to your store currency using the current exchange rate.

Example: Your store currency is EUR. A customer pays $129 USD. Adverfly converts this to ~€119 EUR in your reports.

Timezone

The store_timezone ensures all events and conversions are recorded in your local time, making your reports easier to read and analyze.

Verify Your Installation

Install the Adverfly Pixel Helper Chrome extension to confirm your pixel is firing. The helper captures every beacon the pixel sends, decodes the payload, and shows the event in real time — works on regular sites and inside Shopify Web Pixels.

Tracking Events

Track events and conversions with the Adverfly Pixel

Everything you send through the pixel — pageviews, add-to-carts, purchases, custom events — lands in a single unified events table. The advPxl call has two event types:

  • "event" — interactions without a monetary outcome (add to cart, checkout start, custom)
  • "conversion" — revenue-bearing or goal events (purchase, lead)

Same wire format, same table; the type just controls how the event is treated downstream (attribution windows, dispatch to ad platforms, etc.).

Custom Events

Request
advPxl("event", "add_to_cart");
advPxl("event", "initiated_checkout");

Standard Events

CodeDescription
pageviewUser views a page (tracked automatically on init)
add_to_cartUser adds item to cart
initiated_checkoutUser starts checkout process
purchaseRevenue event — send via advPxl("conversion", "purchase", {...})
leadLead event — send via advPxl("conversion", "lead", {...})

Purchase Conversion

Request
advPxl("conversion", "purchase", {
  transaction_id: "order123",              // required
  transaction_gross_revenue: 10999,        // required (cents)
  transaction_currency: "EUR",             // required
  transaction_shipping_costs: 499,         // optional (cents)
  transaction_tax: 1900,                   // optional (cents)
  transaction_city: "Berlin",              // optional
  transaction_country_code: "DE",          // optional
  transaction_discount_code: "SUMMER20",   // optional
  customer_id: "customer@email.com",       // optional (hashed server-side)
  is_new_customer: 1,                       // optional (1 = new, 0 = returning)
});

Conversion Parameters

ParameterTypeRequiredDescription
transaction_idstringRequiredUnique order ID
transaction_gross_revenuenumberRequiredTotal revenue in cents (e.g., 10999 = €109.99)
transaction_currencystringRequiredCurrency code (EUR, USD, …)
transaction_shipping_costsnumberOptionalShipping cost in cents
transaction_taxnumberOptionalTax amount in cents
transaction_citystringOptionalCustomer's city
transaction_country_codestringOptionalISO country code (DE, US, …)
transaction_discount_codestringOptionalApplied discount/coupon code
customer_idstringOptionalCustomer identifier (e.g., email — SHA-256 hashed server-side)
is_new_customernumberOptional1 = new, 0 = returning

Line Items

Request
advPxl("conversion", "purchase", {
  transaction_id: "order123",
  transaction_gross_revenue: 2000,
  transaction_currency: "EUR",
  transaction_items: [
    {
      transaction_item_id: "123",
      transaction_item_name: "Product 1",
      transaction_item_price: 1000,
      transaction_item_tax: 50,
      transaction_item_quantity: 2,
    },
    {
      transaction_item_id: "124",
      transaction_item_name: "Product 2",
      transaction_item_price: 500,
      transaction_item_quantity: 1,
    },
  ],
});
ParameterTypeRequiredDescription
transaction_item_idstringRequiredProduct / SKU ID
transaction_item_namestringRequiredProduct name
transaction_item_pricenumberRequiredItem price in cents
transaction_item_quantitynumberRequiredQuantity
transaction_item_taxnumberOptionalItem tax in cents

Lead Conversion

Request
advPxl("conversion", "lead", {
  transaction_id: "lead-456",
});

Custom Properties

Any field you pass that isn't one of the reserved keys above is automatically merged into the event's properties JSON (server-side) — usable for filters, breakdowns, and ad-platform CAPI dispatch.

Two equivalent ways to send custom data:

Request
// Inline — non-reserved keys auto-merged into properties
advPxl("conversion", "purchase", {
  transaction_id: "order123",
  transaction_gross_revenue: 10999,
  transaction_currency: "EUR",

  // Anything non-reserved → events.properties JSON
  payment_provider: "klarna_invoice",
  shipping_method: "standard",
  affiliation: "DACH Store",
});

// Or explicit — the `properties` key wins on conflict
advPxl("event", "add_to_cart", {
  properties: {
    product_category: "skincare",
    cart_size: 4,
  },
});

Reserved keys (everything in the parameter tables above plus store_id, event_type, name, customer_id, transaction_*, utm_*, adv_*, form_id, …) stay top-level. Everything else lands in properties.

Query later via JSONExtractString(properties, 'payment_provider') in ClickHouse, or surface as a breakdown in any dashboard.

Autocapture

The v3 pixel can capture user interactions automatically — clicks, form submits, input changes, rage clicks, and copy/cut actions — without manual advPxl calls. Disabled by default: opt in per workspace by setting window.adverfly.activate_autocapture = true before init.

Request
window.adverfly = window.adverfly || [];
window.adverfly.activate_autocapture = true;
advPxl("init", 8397799);

Captured Events

CodeDescription
$clickFired on clicks. Target is resolved to the nearest interactive ancestor (a, button, input, select, textarea, label, form, or role=button|link|menuitem).
$submitFired on form submits. Includes form action and method.
$changeFired on <select> changes and checkbox/radio toggles. Text input values are NEVER captured.
$rageclickFired when the same element is clicked 3 or more times within 1 second.
$copyFired when a user copies content. Only metadata is captured — the copied text itself is never captured.
$cutFired when a user cuts content. Only metadata is captured.

Privacy

Autocapture is designed to never leak sensitive data. The following are enforced client-side before anything is sent:

  • Input types never captured: password, hidden, file.
  • Sensitive fields blocked by name / autocomplete: credit card (cc-*, card-num, card-no), CVC/CVV, expiry, SSN, social, password, API keys, auth tokens, one-time-codes.
  • Freeform input values never read: <input type="text|email|tel|..."> and <textarea> values are never included. Only <select> options and checkbox/radio state are captured.
  • Text scrubbing: any token in visible text that looks like a credit card number, SSN, or a run of 13+ digits is stripped before sending. Result is truncated to 200 characters.
  • Opt-out selectors: elements (and their descendants) marked with class adv-no-capture or attribute data-adv-no-capture are skipped entirely.

Excluding Elements

Exclude a specific element (and all its children) from autocapture:

Request
<!-- Class-based -->
<div class="adv-no-capture">
  <input type="text" name="internal-note" />
</div>

<!-- Attribute-based -->
<section data-adv-no-capture>
  <button>Internal action</button>
</section>

Configuration

Fine-grained control via window.adverfly.autocapture_config (set before init):

CodeDescription
url_allowlistArray of strings (substring match) or RegExp. If set, autocapture runs only on URLs matching at least one entry.
url_ignorelistArray of strings or RegExp. Autocapture is skipped on URLs matching any entry.
element_allowlistArray of lowercase tag names. Only elements with these tags are captured.
css_selector_allowlistArray of CSS selectors. Only elements matching at least one selector are captured.
Request
window.adverfly = window.adverfly || [];
window.adverfly.autocapture_config = {
  url_ignorelist: [/\/admin/, "/debug"],
  css_selector_allowlist: [".track-me", "[data-adv-track]"],
};
advPxl("init", 8397799);

Rate Limiting

Built-in limits protect your site and your event quota:

  • Global: max 30 autocapture events per second.
  • Per element: minimum 500 ms between events on the same element.
  • Rage click: 1 s cooldown per element after a $rageclick fires.

Loyalty Balance Email

If you run an Adverfly loyalty program, customers can request their current balance by email from anywhere in the shop (footer link, customer account page, exit-intent CTA, …). The pixel ships the request to Adverfly; we look the member up, render a branded HTML email with their balance + last 20 transactions, and deliver it via SES from your verified sending domain.

Disabled by default — opt in per shop by setting window.adverfly.activate_loyalty = true before init. This prevents accidental balance mails on shops that don't run a loyalty program.

Request
window.adverfly = window.adverfly || [];
window.adverfly.activate_loyalty = true;
advPxl("init", 8397799);

Drop-in template

Copy-paste this anywhere in your shop (footer, customer-account page, exit-intent modal). It includes the opt-in, a button, the click handler, and a confirmation message. No external dependencies.

Request
<!-- Adverfly Loyalty — Balance check
     Two ways to use this:
       1. Drop the <a> below into your footer / account page.
       2. OR add href="#adv-balance" to ANY existing link in your shop —
          the script below catches it globally. -->
<a href="#adv-balance"
   style="color:inherit;text-decoration:underline;cursor:pointer;">
  Mein Guthaben anzeigen
</a>

<script>
  (function () {
    window.adverfly = window.adverfly || [];
    window.adverfly.activate_loyalty = true;

    function trigger() {
      var email = (window.adverfly && window.adverfly.customer_id)
        || prompt("Bitte gib deine E-Mail-Adresse ein:");
      if (!email) return;
      advPxl("balance", { email: email });
      alert("Danke! Falls deine Adresse bei uns hinterlegt ist, erhältst du dein Guthaben in wenigen Minuten per E-Mail.");
    }

    document.addEventListener("click", function (e) {
      var t = e.target;
      if (!t || !t.closest) return;
      var link = t.closest('a[href$="#adv-balance"], #adv-loyalty-balance');
      if (!link) return;
      e.preventDefault();
      trigger();
    });
  })();
</script>

Logged-in shoppers

If your platform already identifies the customer (Shopify customer accounts set window.adverfly.customer_id to the email), omit the email arg — the pixel uses the known identity automatically.

Request
window.adverfly.customer_id = "kunde@example.com"; // typically set by the platform
advPxl("balance"); // no second arg needed

Shopify Liquid

If you're on a Shopify theme, you can render the button conditionally so guests still see a prompt while logged-in customers skip the input step.

Request
<button onclick="advPxl('balance', { email: '{{ customer.email | default: '' }}' || prompt('E-Mail eingeben:') })">
  Mein Guthaben anzeigen
</button>
<script>
  window.adverfly = window.adverfly || [];
  window.adverfly.activate_loyalty = true;
</script>

Behaviour

  • Always returns 200 — the response never reveals whether the email is on file (anti-enumeration).
  • No on-page feedback — pixel doesn't surface success / error. Show your own "Wenn du Mitglied bist, kommt die Mail in wenigen Minuten" message after the click.
  • Bounce-protected — addresses on the SES bounce/complaint list are silently skipped to protect your sending reputation.
  • From-address — uses your verified domain if configured (Loyalty → Email Sender), otherwise falls back to the Adverfly default.

Test from the dashboard

In Loyalty → Settings → Preview balance email, type an existing member email and click "Send to me". You'll receive a copy of the exact email that member would get, labeled [Test] with a yellow banner so it can't be confused with a live send. Useful for QAing design changes without triggering real customer mail.

Google Tag Manager

Install Adverfly via Google Tag Manager

You can install the Adverfly Pixel using Google Tag Manager for easier management.

Installation Steps

  1. Open your GTM container
  2. Create a new Tag
  3. Choose Custom HTML
  4. Paste the following code:
Request
<script>
  window.adverfly = window.adverfly || [];
  function advPxl() {
    var args = [false];
    for (var i = 0; i < arguments.length; i++) {
      args.push(arguments[i]);
    }
    adverfly.push(args);
  }
  advPxl("init", YOUR_WORKSPACE_ID);
  window.adverfly.store_currency = "EUR";
  window.adverfly.store_timezone = "Europe/Berlin";

  var script = document.createElement("script");
  script.type = "text/javascript";
  script.async = true;
  script.src = "https://sos-de-fra-1.exo.io/adv/advv2.01.js";
  document.getElementsByTagName("head")[0].appendChild(script);
</script>
  1. Set the trigger to All Pages
  2. Save and publish

Configuration

CodeDescription
store_currencyYour store's base currency. Transactions in other currencies are auto-converted.
store_timezoneYour store's timezone for accurate event timestamps in reports.

Tracking Conversions via Data Layer

Quick version — minimal mapping from the GA4-style purchase event:

Request
<script>
  // Fires on the purchase confirmation page
  advPxl("conversion", "purchase", {
    transaction_id: {{DL - Transaction ID}},
    transaction_gross_revenue: {{DL - Revenue}} * 100,
    transaction_currency: {{DL - Currency}}
  });
</script>

Full dataLayer Mapping

Most shops (Shopware, Shopify GTM, WooCommerce GA4) push a richer purchase event into the dataLayer. The pattern below subscribes to it and maps every useful field — line items, payment provider, shipping method, country, hashed email.

Drop this into a Custom HTML tag with the All Pages trigger; it auto-handles all future purchases without per-page tags.

Request
<script>
  if (window.dataLayer) {
    function advForwardPurchase(event) {
      if (!event || event.event !== "purchase") return;
      var ec = event.ecommerce || {};

      // "9.99" / 9.99 → 999 (cents). toFixed handles float drift,
      // replace strips the decimal point.
      var toCents = function (v) {
        return Number(v || 0).toFixed(2).toString().replace(/\D/g, "") || 0;
      };

      // pageCountryCode is shipped as "de_DE" — take the country part.
      var pageCC = (window.dataLayer || [])
        .map(function (e) { return e && e.pageCountryCode; })
        .filter(Boolean).pop() || "";
      var countryCode = pageCC.split("_").pop().toUpperCase().slice(0, 2);

      advPxl("conversion", "purchase", {
        // Reserved keys → typed columns
        transaction_id: ec.transaction_id || event.transaction_id,
        transaction_gross_revenue: toCents(ec.value || event.value),
        transaction_currency: ec.currency || event.currency,
        transaction_tax: toCents(ec.tax || event.tax),
        transaction_shipping_costs: toCents(ec.shipping || event.shipping),
        transaction_country_code: countryCode,
        customer_id: event.transactionEmail || ec.customer_id || "",

        transaction_items: (ec.items || []).map(function (it) {
          return {
            transaction_item_id: it.item_id || it.id,
            transaction_item_name: it.item_name || it.name,
            transaction_item_price: toCents(it.price),
            transaction_item_quantity: Number(it.quantity || 1),
          };
        }),

        // Non-reserved keys → events.properties JSON (auto-merged server-side)
        payment_provider: event.transactionPaymentType || "",
        shipping_method: event.transactionShippingMethod || "",
        affiliation: event.transactionAffiliation || "",
      });
    }

    // Hook every future push + replay anything already in the queue
    var originalPush = window.dataLayer.push;
    window.dataLayer.push = function () {
      var args = [].slice.call(arguments);
      originalPush.apply(this, args);
      args.forEach(advForwardPurchase);
    };
    window.dataLayer.forEach(advForwardPurchase);
  }
</script>

The pattern is the same for add_to_cart, begin_checkout, or any other dataLayer event — switch on event.event and call advPxl("event", "<name>").

Tracking Custom Events

Create additional tags for custom events:

Request
<script>
  // Add to Cart tag - trigger on add_to_cart event
  advPxl("event", "add_to_cart");
</script>
Request
<script>
  // Initiated Checkout tag - trigger on checkout start
  advPxl("event", "initiated_checkout");
</script>
CodeDescription
Base PixelAll Pages
Purchase Conversionpurchase event / thank you page
Add to Cartadd_to_cart event
Initiated Checkoutinitiated_checkout event

Shopify Integration

Install Adverfly on your Shopify store

Adverfly integrates natively with Shopify's Customer Events API for accurate tracking. If you also want to render personalization widgets (popups, banners, countdowns, toasts) on your storefront, a second tiny step is required — see "Optional: Enable Widgets" at the bottom of this page.

Installation Steps

  1. Go to your Shopify Admin
  2. Navigate to SettingsCustomer events
  3. Click Add custom pixel
  4. Name it "Adverfly"
  5. Paste the following code:
Request
const script = document.createElement("script");
script.type = "text/javascript";
script.async = true;
script.src = "https://sos-de-fra-1.exo.io/adv/script-shopify.js";
document.getElementsByTagName("script")[0].parentNode.appendChild(script);

window.adverfly_web_pixel = true;
window.adverfly_init = init;
window.adverfly_browser = browser;
window.adverfly_settings = api.settings;

window.adverfly = window.adverfly || [];
window.advPxl = function () {
  adverfly.push([false, ...arguments]);
};

analytics.subscribe("all_events", (event) => {
  window.adverfly.push(["all_shopify_events", event]);
  advPxl("init", YOUR_WORKSPACE_ID);
  window.adverfly.store_currency = "EUR";
  window.adverfly.store_timezone = "Europe/Berlin";

  if (event.name === "page_viewed") {
    advPxl("check", "vikeys", event);
  }
});

Replace YOUR_WORKSPACE_ID with your Workspace ID and set your store's currency and timezone.

Configuration

CodeDescription
store_currencyYour store's base currency. Transactions in other currencies are auto-converted.
store_timezoneYour store's timezone for accurate event timestamps in reports.

Tracked Events

The Shopify integration automatically tracks all standard e-commerce events:

CodeDescription
page_viewed → pageviewShopify page_viewed event, recorded as pageview in Adverfly
product_added_to_cart → add_to_cartShopify add to cart event, recorded as add_to_cart in Adverfly
checkout_started → initiated_checkoutShopify checkout start, recorded as initiated_checkout in Adverfly
checkout_completed → purchaseShopify purchase event, recorded as purchase conversion in Adverfly

Debug Your Pixel

Shopify Web Pixels run inside a sandboxed iframe, so the browser's standard Network tab can be a pain to inspect. The Adverfly Pixel Helper Chrome extension captures every beacon the pixel sends — including from inside the Shopify sandbox — and shows the decoded event payload in a popup. Recommended for verifying the install before going live.

Optional: Enable Widgets

The Shopify Web Pixel above runs in a sandboxed iframe and can therefore track events but cannot render UI in your storefront. To enable Adverfly personalization widgets (popups, banners, countdowns, toasts), add a second tiny snippet to your theme — same pattern as Google Analytics or any other tag.

  1. In your Shopify Admin, open Online Store → Themes → ⋯ → Edit code
  2. Open layout/theme.liquid
  3. Paste this just before </head>:
Request
<script async src="https://cdn.adverfly.com/preset-pixel-adv.js"></script>
<script>
  window.adverfly = window.adverfly || [];
  window.adverfly.activate_widgets = true;
  advPxl("init", YOUR_WORKSPACE_ID);
</script>

The theme pixel auto-detects that you're on Shopify and skips firing pageview / vikey events — the Web Pixel above already tracks those. The theme pixel exists solely to render widgets in the storefront DOM. No double-counting.

If you only want tracking (no widgets), skip this section. The Web Pixel covers tracking on its own.

Personalization SDK

Headless JS SDK to render Adverfly widgets with your own components

The @adverfly/sdk JavaScript package lets you fetch the right personalized variant for the current visitor and render it however you want — your own React/Vue/Svelte components, server-rendered HTML, or even mobile apps.

The standard tracking pixel still works as a no-code option. Use the SDK when you need:

  • Pixel-perfect brand control — no iframe, no CSS overrides
  • Server-side / Edge personalization — Cloudflare Workers, Vercel Edge, Next.js Server Components
  • Custom UI — inline blocks, mobile screens, anything beyond popup/banner/toast/countdown
  • Strict typing — your config shape becomes a TypeScript generic

Install

Three formats, same code — pick whichever fits your stack.

Vanilla <script> (no build step)

Request
<script src="https://cdn.jsdelivr.net/npm/@adverfly/sdk/dist/adverfly.iife.js"></script>
<script>
  const adv = new Adverfly({ workspaceId: 188334 });
  /* `Adverfly` is now a global class — no module system required. */
</script>

Pin a version in production: @adverfly/sdk@0.1.0/dist/adverfly.iife.js.

ES modules in the browser

Request
<script type="module">
  import { Adverfly } from "https://cdn.jsdelivr.net/npm/@adverfly/sdk/dist/index.mjs";
  const adv = new Adverfly({ workspaceId: 188334 });
</script>

npm (build pipelines, Node, SSR)

Request
npm install @adverfly/sdk
import { Adverfly } from "@adverfly/sdk";

Works in browsers and Node 18+. Zero peer dependencies. Bundle is ~6 KB minified for the IIFE build, ~12 KB for the ESM build.

Quick start

Request
import { Adverfly } from "@adverfly/sdk";

const adv = new Adverfly({ workspaceId: 188334 });

await adv.identify({ email: "user@example.com" });

adv.setContext({
  cart_value: 49.9,
  last_viewed_creatives: ["sku_a", "sku_b"],
});

const variant = await adv.personalize({ trigger: "exit_intent" });

if (variant) {
  showMyPopup({
    title: variant.config.title,
    copy: variant.config.copy,
    onCtaClick: () => adv.click(variant.id),
    onDismiss: () => adv.dismiss(variant.id),
  });
  await adv.trackImpression(variant.id);
}

Constructor

ParameterTypeRequiredDescription
workspaceIdnumberRequiredYour Adverfly workspace ID.
apiUrlstringOptionalOverride for self-hosted or staging. Defaults to https://b.adverfly.com.
customerIdstringOptionalPre-identify without calling identify().
debugbooleanOptionalMirror decisions + events to console.log under [adverfly].
manualIdentitybooleanOptionalSkip auto-anonymous-id (for SSR / when you manage identity).

Identity

await adv.identify({
  email: "user@example.com",   // hashed (SHA-256, lowercased) → never sent raw
  transactionId: "order_123",  // optional, for post-purchase triggers
});

To reset identity (e.g. on logout):

adv.reset();

Personalize

const variant = await adv.personalize<MyConfigShape>({
  trigger: "exit_intent",
  surface: "widget",  // optional, defaults to "widget"
  context: { device_battery_low: true },  // merged on top of session context
});
// { id, config, reason } | null

Strongly-typed config:

Request
interface PopupConfig {
  title: string;
  copy: string;
  cta?: string;
  cta_url?: string;
  image_url?: string;
}

const variant = await adv.personalize<PopupConfig>({ trigger: "exit_intent" });
if (variant) {
  console.log(variant.config.title); // typed!
}

Tracking

All tracking events are written to the same ClickHouse pixel_events table the standard pixel uses — joinable by customer_id (hashed) for conversion attribution.

CodeDescription
trackImpression(variantId)Widget rendered into your UI.
click(variantId, props?)User clicked the CTA. props can include cta_url.
dismiss(variantId)User closed the widget manually.
autoDismissed(variantId, reason?)Timer expired (countdown, toast).
success(variantId, props?)Goal completed. If props.email is set, auto-hashed before send.

Events

Subscribe to the lifecycle for analytics, debugging, or custom rendering hooks.

Request
const off = adv.on("decision", ({ trigger, variant }) => {
  console.log(`[${trigger}] →`, variant?.id ?? "no match");
});

/* Returns an unsubscribe function */
off();

Available events: decision, impression, click, dismiss, auto_dismissed, success, error.

React

A first-class @adverfly/react package is planned. For now you can wrap the core SDK in a hook:

Request
import { Adverfly, type Variant } from "@adverfly/sdk";

const AdverflyContext = createContext<Adverfly | null>(null);

export function usePersonalization<T>(trigger: string) {
  const adv = useContext(AdverflyContext)!;
  const [variant, setVariant] = useState<Variant<T> | null>(null);

  useEffect(() => {
    adv.personalize<T>({ trigger }).then((v) => {
      setVariant(v);
      if (v) adv.trackImpression(v.id);
    });
  }, [adv, trigger]);

  return {
    variant,
    click: (props?) => variant && adv.click(variant.id, props),
    dismiss: () => variant && adv.dismiss(variant.id),
    success: (props?) => variant && adv.success(variant.id, props),
  };
}

Full example: sdks/javascript/examples/react-hook.tsx.

Server-side / Edge

Works anywhere fetch + crypto.subtle exist (Node 18+, Bun, Cloudflare Workers, Vercel Edge).

Request
/* Cloudflare Worker — server-render a personalized hero block */
export default {
  async fetch(request: Request) {
    const adv = new Adverfly({
      workspaceId: 188334,
      manualIdentity: true,  // we manage identity ourselves
    });
    await adv.identify({ customerId: getCookieUserId(request) });

    const variant = await adv.personalize({
      trigger: "ssr_hero",
      context: { country: request.cf?.country },
    });

    return new Response(renderHero(variant?.config), {
      headers: { "content-type": "text/html" },
    });
  },
};

Privacy

  • Emails are hashed client-side (SHA-256, lowercased + trimmed) before any network call.
  • No cookies set by the SDK. Anonymous IDs go to localStorage; opt out with manualIdentity: true.
  • CORS-clean. POST + JSON, no preflight surprises.

See also

  • Widgets — the no-code drop-in pixel option
  • Tracking Events — base event schema (the SDK rides on the same beacon)