Langsung ke konten utama

Documentation Index

Fetch the complete documentation index at: https://docs.scalev.com/llms.txt

Use this file to discover all available pages before exploring further.

Storefront API menyediakan endpoint konversi server-side untuk Meta, TikTok, dan SnackVideo. Endpoint ini hanya berperan sebagai server relay. Storefront Anda tetap mengatur bagian browser: memuat pixel, menyimpan ID atribusi, memilih event konversi yang dikirim, membuat payload commerce, dan memakai event ID yang sama di browser pixel serta event server Scalev untuk deduplikasi. Gunakan halaman ini saat storefront custom Anda dipakai untuk iklan.

Cara alurnya bekerja

Untuk setiap konversi yang ingin dilacak:
  1. Muat browser pixel dari platform iklan.
  2. Ambil ID atribusi dari URL landing dan cookie.
  3. Buat parameter event dan payload item.
  4. Buat satu event ID untuk Meta atau TikTok.
  5. Kirim event browser pixel dengan event ID tersebut.
  6. Kirim event yang sama ke endpoint analytics Storefront API Scalev.
Scalev kemudian mengirim event server-side ke pixel yang sudah dikonfigurasi di pengaturan Storefront analytics toko. Route Storefront API memilih toko dari /v3/stores/{store_id} dan X-Scalev-Storefront-Api-Key. Jangan mengirim custom domain atau page ID untuk mengidentifikasi toko.
POST /v3/stores/{store_id}/public/analytics/meta/events
POST /v3/stores/{store_id}/public/analytics/tiktok/events
POST /v3/stores/{store_id}/public/analytics/snackvideo/events
Ketiga endpoint membutuhkan publishable Storefront API key:
const API_BASE = "https://api.scalev.com";
const STORE_ID = "store_xxx";
const STOREFRONT_KEY = "sfpk_xxx";

const analyticsPaths = {
  meta: "/public/analytics/meta/events",
  tiktok: "/public/analytics/tiktok/events",
  snackvideo: "/public/analytics/snackvideo/events",
};

async function postScalevAnalytics(provider, payload) {
  const response = await fetch(
    `${API_BASE}/v3/stores/${STORE_ID}${analyticsPaths[provider]}`,
    {
      method: "POST",
      credentials: "omit",
      headers: {
        "Accept": "application/json",
        "Content-Type": "application/json",
        "X-Scalev-Storefront-Api-Key": STOREFRONT_KEY,
      },
      body: JSON.stringify(payload),
    }
  );

  if (!response.ok) {
    throw new Error(`Scalev analytics request failed: ${response.status}`);
  }
}
Endpoint analytics mengembalikan 204 No Content setelah menerima event untuk dikirim secara asynchronous.

Konfigurasi browser pixel

Server relay tidak menggantikan browser pixel. Anda tetap membutuhkan browser pixel untuk atribusi, optimasi, dan deduplikasi Meta/TikTok. Gunakan pixel ID yang sama dengan pixel yang dikonfigurasi di Scalev Storefront analytics. Pixel ID adalah identifier publik, jadi Anda dapat menyimpannya di konfigurasi frontend:
const META_PIXEL_IDS = ["1234567890"];
const TIKTOK_PIXEL_IDS = ["CABCDEF123456"];
const SNACKVIDEO_PIXEL_IDS = ["KWAI_PIXEL_ID"];
Muat SDK browser resmi setiap provider satu kali, lalu inisialisasi pixel yang dikonfigurasi:
function initPixels() {
  if (window.__scalevStorefrontPixelsInitialized) return;
  window.__scalevStorefrontPixelsInitialized = true;

  for (const pixelId of META_PIXEL_IDS) {
    window.fbq?.("init", pixelId);
  }
  window.fbq?.("track", "PageView");

  for (const pixelId of TIKTOK_PIXEL_IDS) {
    window.ttq?.load(pixelId);
  }
  window.ttq?.page?.();

  for (const pixelId of SNACKVIDEO_PIXEL_IDS) {
    window.kwaiq?.load(pixelId);
  }
  window.kwaiq?.page?.();
}
Jika Anda memakai Google Tag Manager atau tag manager lain, atur dengan prinsip yang sama: event browser dan event server Scalev harus memakai nama event dan event ID yang sama untuk satu aksi pembeli.

Izinkan domain storefront di Meta

Meta memakai Traffic permissions di Events Manager untuk mengatur domain mana yang boleh mengirim event lewat Meta pixel. Jika allow list Meta aktif dan domain storefront Anda tidak ada di daftar tersebut, Meta akan memblokir event dari domain itu. Pixel masih bisa terlihat firing di browser, dan Scalev masih bisa menerima request server relay, tetapi event tidak akan tercatat oleh Meta. Selesaikan ini sebelum menjalankan iklan atau memvalidasi event produksi.
  1. Buka Meta Events Manager.
  2. Pilih pixel atau dataset yang dipakai oleh pengaturan Storefront analytics toko.
  3. Buka Settings.
  4. Di Traffic permissions, buat atau kelola allow list.
  5. Tambahkan domain storefront produksi yang dikunjungi pembeli, seperti shop.example.com atau example.com.
  6. Konfirmasi perubahan, lalu uji event storefront nyata di Meta Test events.
Jika Anda menambahkan top-level domain seperti example.com, Meta juga mencakup subdomainnya, seperti shop.example.com. Jika Anda tidak ingin semua subdomain diterima, tambahkan subdomain yang spesifik satu per satu. Jangan memasukkan domain staging, preview, atau localhost ke allow list pixel produksi kecuali event dari domain tersebut memang harus diterima oleh dataset produksi. Saat mengirim server event lewat Scalev, pastikan event_source_url memakai domain storefront yang sama dengan domain yang Anda izinkan di Meta. Lihat dokumentasi Meta tentang pengelolaan pixel traffic permissions dan praktik terbaik traffic permissions.

Ambil ID atribusi

Ambil ad click ID segera saat app berjalan. Storefront bawaan Scalev menyimpan nilai berikut:
SumberQuery parameterCookieDipakai oleh
Meta click IDfbclid_fbcMeta Conversions API
Meta browser IDDibuat saat belum ada_fbpMeta Conversions API
TikTok click IDttclid_ttclidTikTok Events API
TikTok browser IDDibuat oleh TikTok pixel_ttpTikTok Events API
SnackVideo click IDclick_id_kwai_click_idSnackVideo Events API
Simpan click ID selama tujuh hari. Meta browser ID biasanya disimpan lebih lama karena browser pixel Meta memakainya lintas kunjungan.
function getCookie(name) {
  return document.cookie
    .split("; ")
    .find((row) => row.startsWith(`${name}=`))
    ?.split("=")[1];
}

function setCookie(name, value, maxAgeDays) {
  if (!value) return;

  const secure = location.protocol === "https:" ? "Secure" : "";
  document.cookie = [
    `${name}=${encodeURIComponent(value)}`,
    `Max-Age=${maxAgeDays * 24 * 60 * 60}`,
    "Path=/",
    "SameSite=Lax",
    secure,
  ].filter(Boolean).join("; ");
}

export function captureAdAttribution() {
  const params = new URLSearchParams(location.search);
  const now = Date.now();

  const fbclid = params.get("fbclid");
  if (fbclid) {
    setCookie("_fbc", `fb.1.${now}.${fbclid}`, 90);
  }

  if (!getCookie("_fbp")) {
    const random = Math.floor(Math.random() * 10000000000);
    setCookie("_fbp", `fb.1.${now}.${random}`, 90);
  }

  setCookie("_ttclid", params.get("ttclid"), 7);
  setCookie("_kwai_click_id", params.get("click_id"), 7);
}

export function currentAttribution() {
  const params = new URLSearchParams(location.search);

  return {
    fbc: decodeURIComponent(getCookie("_fbc") || ""),
    fbp: decodeURIComponent(getCookie("_fbp") || ""),
    ttclid:
      params.get("ttclid") ||
      decodeURIComponent(getCookie("_ttclid") || ""),
    ttp: decodeURIComponent(getCookie("_ttp") || ""),
    snackVideoClickId:
      params.get("click_id") ||
      decodeURIComponent(getCookie("_kwai_click_id") || ""),
  };
}
Jalankan captureAdAttribution() sebelum Anda mengirim event page, produk, cart, checkout, atau order.

Pilih event

Kirim event setelah aksi pembeli berhasil. Jangan mengirim event konversi sebelum aksi API yang diwakili event tersebut selesai.
Momen storefrontKirim setelahEvent MetaEvent TikTokEvent SnackVideo
Detail produk atau bundle terlihatResponse API detail sudah direnderViewContentViewContentEvent content-view yang dikonfigurasi
Item ditambahkan ke cartPOST /public/cart/items berhasilAddToCartAddToCartEvent add-to-cart yang dikonfigurasi
Checkout dimulaiHalaman atau form checkout terbuka dengan konteks itemInitiateCheckoutInitiateCheckoutEvent checkout yang dikonfigurasi
Pembeli memilih metode pembayaranPayment selector dibuka atau metode dipilihEvent opsional yang dikonfigurasiEvent opsional yang dikonfigurasiEvent opsional yang dikonfigurasi
Order dibuatPOST /public/checkout atau POST /customers/me/checkout berhasilPurchase atau event submit yang dikonfigurasiCompletePayment atau event submit yang dikonfigurasiEvent purchase yang dikonfigurasi
Halaman payment success terlihatHanya jika Anda belum mengirim konversi orderPurchaseCompletePaymentEvent purchase yang dikonfigurasi
Pilih satu titik konversi untuk optimasi purchase. Jika Anda mengirim purchase saat order dibuat, jangan mengirim purchase lagi di halaman payment. Jika Anda hanya mengoptimasi order yang sudah dibayar, kirim purchase setelah konfirmasi pembayaran, bukan saat order dibuat. Untuk TikTok, gunakan nama event yang dikonfigurasi di TikTok Events Manager. Untuk optimasi purchase, TikTok umumnya memakai CompletePayment. Untuk SnackVideo, gunakan nama event yang persis dikonfigurasi untuk toko. Storefront API meneruskan nama event yang Anda kirim.

Buat payload item

Payload analytics Scalev memakai item unique ID, bukan numeric ID yang dipakai oleh input checkout item. Untuk product variant:
{
  "variant_unique_id": "variant_xxx",
  "quantity": 1
}
Untuk bundle price option:
{
  "bundle_price_option_unique_id": "bundle_price_option_xxx",
  "quantity": 1
}
Buat payload item analytics dari produk, bundle price option, cart row, atau checkout row yang menjadi konteks event:
function buildAnalyticsItems(items) {
  const variants = [];
  const bundle_price_options = [];

  for (const item of items) {
    if (item.type === "variant") {
      variants.push({
        variant_unique_id: item.variant_unique_id,
        quantity: item.quantity,
      });
    }

    if (item.type === "bundle_price_option") {
      bundle_price_options.push({
        bundle_price_option_unique_id: item.bundle_price_option_unique_id,
        quantity: item.quantity,
      });
    }
  }

  return {
    variants: variants.length > 0 ? variants : undefined,
    bundle_price_options:
      bundle_price_options.length > 0 ? bundle_price_options : undefined,
  };
}
Untuk event detail produk, kirim variant yang terlihat dengan quantity 1. Untuk event checkout atau purchase, kirim semua item di checkout.

Buat parameter event

Kirim parameter commerce yang ramah untuk setiap provider. Untuk Meta, parameter umum adalah:
{
  content_ids: ["variant_xxx"],
  content_type: "product",
  contents: [
    {
      id: "variant_xxx",
      quantity: 1,
      item_price: 150000,
      delivery_category: "home_delivery",
    },
  ],
  content_name: "Black T-Shirt",
  content_category: "Apparel",
  value: 150000,
  currency: "IDR",
  num_items: 1,
}
Untuk TikTok, sertakan content_id yang stabil jika memungkinkan. Storefront bawaan mengambilnya dari konteks produk, bundle, atau halaman saat payload event tidak menyertakannya.
{
  content_id: "product_or_bundle_xxx",
  content_type: "product",
  contents: [
    {
      content_id: "variant_xxx",
      quantity: 1,
      price: 150000,
    },
  ],
  value: 150000,
  currency: "IDR",
}
Untuk SnackVideo, kirim properti seperti:
{
  content_type: "product",
  content_category: "Apparel",
  content_name: "Black T-Shirt",
  currency: "IDR",
  value: 150000,
  quantity: 1,
  price: 150000,
}
Gunakan format integer yang sama dengan total IDR yang Anda tampilkan di storefront.

Deduplikasi event Meta dan TikTok

Meta dan TikTok melakukan deduplikasi memakai event ID. Buat satu event ID untuk satu aksi pembeli, lalu pakai ulang di dua tempat:
  • Meta browser pixel: eventID
  • Payload Meta Storefront API: events[].event_id
  • TikTok browser pixel: event_id
  • Payload TikTok Storefront API: events[].event_id
Untuk event page, produk, cart, dan interaksi checkout, buat event ID random:
function newEventId(prefix = "evt") {
  if (crypto.randomUUID) return `${prefix}_${crypto.randomUUID()}`;
  return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2)}`;
}
Untuk event submit order, pakai ID deterministik berdasarkan order. Ini mencegah purchase dobel jika pembeli retry request atau reload halaman success.
function orderEventId(order, eventName) {
  return `${order.order_id}-${eventName}-FORM`;
}
Payload Storefront API SnackVideo saat ini tidak memakai event_id. Untuk SnackVideo, pakai click_id untuk atribusi dan pastikan frontend tidak mengirim konversi yang sama dua kali.

Kirim event Meta

async function trackMetaEvent({
  eventName,
  eventId,
  parameters,
  userData = {},
  items = {},
}) {
  const attribution = currentAttribution();

  window.fbq?.("track", eventName, parameters, {
    eventID: eventId,
  });

  await postScalevAnalytics("meta", {
    event_source_url: location.href,
    referrer_url: document.referrer || undefined,
    user_data: {
      country: "id",
      fbc: attribution.fbc || undefined,
      fbp: attribution.fbp || undefined,
      ...userData,
    },
    events: [
      {
        event_id: eventId,
        event_name: eventName,
        parameters,
      },
    ],
    ...items,
  });
}
Meta user data dapat berisi fn, ln, em, ph, ct, st, country, external_id, location_id, fbc, dan fbp. Scalev mengisi client_ip_address dan client_user_agent dari request API yang diterima, jadi Anda tidak perlu mengirimnya dari browser.

Kirim event TikTok

async function trackTiktokEvent({
  eventName,
  eventId,
  parameters,
  user = {},
  items = {},
}) {
  const attribution = currentAttribution();

  window.ttq?.track(eventName, parameters, {
    event_id: eventId,
  });

  await postScalevAnalytics("tiktok", {
    event_source_url: location.href,
    referrer_url: document.referrer || undefined,
    user: {
      ttclid: attribution.ttclid || undefined,
      ttp: attribution.ttp || undefined,
      ...user,
    },
    events: [
      {
        event_id: eventId,
        event: eventName,
        parameters,
      },
    ],
    ...items,
  });
}
TikTok user data dapat berisi email, phone, external_id, ttclid, dan ttp. Scalev mengisi ip dari request API yang diterima dan memakai user_agent dari request saat Anda tidak mengirimkannya.

Kirim event SnackVideo

async function trackSnackVideoEvent({
  eventName,
  properties,
  items = {},
}) {
  const attribution = currentAttribution();

  for (const pixelId of SNACKVIDEO_PIXEL_IDS) {
    window.kwaiq?.instance?.(pixelId)?.track(eventName, properties);
  }

  if (!attribution.snackVideoClickId) return;

  await postScalevAnalytics("snackvideo", {
    click_id: attribution.snackVideoClickId,
    event_source_url: location.href,
    referrer_url: document.referrer || undefined,
    events: [
      {
        event_name: eventName,
        properties,
      },
    ],
    ...items,
  });
}
Server event SnackVideo membutuhkan click_id. Jika pembeli tidak datang dengan click_id, tetap kirim browser pixel event saat dikonfigurasi, tetapi lewati server event.

Contoh: add to cart

Kirim ini setelah POST /public/cart/items berhasil:
async function trackAddToCart(cartItem) {
  const item = {
    type: "variant",
    variant_unique_id: cartItem.variant.unique_id,
    quantity: cartItem.quantity,
  };

  const items = buildAnalyticsItems([item]);
  const parameters = {
    content_ids: [item.variant_unique_id],
    content_type: "product",
    contents: [
      {
        id: item.variant_unique_id,
        quantity: item.quantity,
        item_price: cartItem.unit_price,
      },
    ],
    value: cartItem.unit_price * cartItem.quantity,
    currency: "IDR",
    num_items: item.quantity,
  };

  const eventId = newEventId("add_to_cart");

  await Promise.all([
    trackMetaEvent({
      eventName: "AddToCart",
      eventId,
      parameters,
      items,
    }),
    trackTiktokEvent({
      eventName: "AddToCart",
      eventId,
      parameters: {
        ...parameters,
        content_id: item.variant_unique_id,
      },
      items,
    }),
    trackSnackVideoEvent({
      eventName: "addToCart",
      properties: parameters,
      items,
    }),
  ]);
}

Contoh: konversi checkout

Kirim konversi purchase setelah checkout berhasil, kecuali strategi iklan Anda hanya menghitung order yang sudah dibayar.
async function trackOrderCreated(order, checkoutItems, buyer) {
  const items = buildAnalyticsItems(checkoutItems);
  const value = order.gross_revenue || order.total_price;

  const parameters = {
    content_ids: checkoutItems.map(
      (item) => item.variant_unique_id || item.bundle_price_option_unique_id
    ),
    content_type: "product",
    contents: checkoutItems.map((item) => ({
      id: item.variant_unique_id || item.bundle_price_option_unique_id,
      quantity: item.quantity,
      item_price: item.unit_price,
    })),
    value,
    currency: "IDR",
    num_items: checkoutItems.reduce((total, item) => total + item.quantity, 0),
  };

  await Promise.all([
    trackMetaEvent({
      eventName: "Purchase",
      eventId: orderEventId(order, "Purchase"),
      parameters,
      userData: {
        em: buyer.email,
        ph: buyer.phone,
        fn: buyer.firstName,
        ln: buyer.lastName,
        ct: buyer.city,
        st: buyer.province,
        external_id: String(order.customer_id || buyer.customerId || ""),
      },
      items,
    }),
    trackTiktokEvent({
      eventName: "CompletePayment",
      eventId: orderEventId(order, "CompletePayment"),
      parameters: {
        ...parameters,
        content_id: parameters.content_ids[0],
      },
      user: {
        email: buyer.email,
        phone: buyer.phone,
        external_id: String(order.customer_id || buyer.customerId || ""),
      },
      items,
    }),
    trackSnackVideoEvent({
      eventName: "purchase",
      properties: parameters,
      items,
    }),
  ]);
}
Jika response order atau pengecekan fraud Anda menandai order sebagai spam, lewati event konversi purchase.

Referensi payload

Request Meta:
{
  "event_source_url": "https://shop.example.com/products/black-shirt",
  "referrer_url": "https://facebook.com/",
  "user_data": {
    "country": "id",
    "em": "buyer@example.com",
    "ph": "628123456789",
    "fbc": "fb.1.1710000000000.fbclid",
    "fbp": "fb.1.1710000000000.1234567890",
    "external_id": "customer_123"
  },
  "events": [
    {
      "event_id": "add_to_cart_123",
      "event_name": "AddToCart",
      "parameters": {
        "content_ids": ["variant_xxx"],
        "content_type": "product",
        "value": 150000,
        "currency": "IDR"
      }
    }
  ],
  "variants": [
    {
      "variant_unique_id": "variant_xxx",
      "quantity": 1
    }
  ]
}
Request TikTok:
{
  "event_source_url": "https://shop.example.com/products/black-shirt",
  "referrer_url": "https://tiktok.com/",
  "user": {
    "email": "buyer@example.com",
    "phone": "628123456789",
    "ttclid": "ttclid_value",
    "ttp": "ttp_cookie_value",
    "external_id": "customer_123"
  },
  "events": [
    {
      "event_id": "add_to_cart_123",
      "event": "AddToCart",
      "parameters": {
        "content_id": "variant_xxx",
        "content_type": "product",
        "value": 150000,
        "currency": "IDR"
      }
    }
  ],
  "variants": [
    {
      "variant_unique_id": "variant_xxx",
      "quantity": 1
    }
  ]
}
Request SnackVideo:
{
  "click_id": "snackvideo_click_id",
  "event_source_url": "https://shop.example.com/products/black-shirt",
  "referrer_url": "https://snackvideo.com/",
  "events": [
    {
      "event_name": "addToCart",
      "properties": {
        "content_type": "product",
        "content_name": "Black T-Shirt",
        "value": 150000,
        "currency": "IDR",
        "quantity": 1
      }
    }
  ],
  "variants": [
    {
      "variant_unique_id": "variant_xxx",
      "quantity": 1
    }
  ]
}

Checklist produksi

  • Muat browser pixel satu kali per page session.
  • Ambil fbclid, ttclid, dan click_id sebelum mengirim event.
  • Pertahankan _fbp, _fbc, _ttp, _ttclid, dan _kwai_click_id.
  • Tambahkan domain storefront produksi ke Meta pixel traffic allow list sebelum memvalidasi event Meta.
  • Kirim event hanya setelah aksi API yang diwakili event tersebut berhasil.
  • Pakai satu event ID untuk copy browser dan server dari setiap event Meta atau TikTok.
  • Pakai event ID deterministik untuk event purchase.
  • Kirim event_source_url dan referrer_url agar Scalev dapat memilih konfigurasi checkout atau home storefront analytics.
  • Kirim variants atau bundle_price_options dengan item unique ID dan quantity.
  • Lewati server event SnackVideo saat tidak ada click_id.
  • Jangan menahan checkout jika request analytics gagal. Log error tersebut dan biarkan pembeli melanjutkan.