Skip to main content

Web Pixel Extensions

Web pixel 확장은 고객 이벤트 추적 및 분석에 대한 Shopify의 개인정보 보호 우선 접근 방식입니다. 스토어프론트에 임의의 JavaScript를 주입하는 대신(성능 저하 및 개인정보 침해 위험이 있음), web pixel은 고객 데이터에 대한 제어된 접근이 가능한 샌드박스 환경에서 실행됩니다. 표준화된 전자상거래 이벤트를 수신하고 고객 동의 설정을 준수하면서 분석 플랫폼에 데이터를 전달할 수 있습니다.

Web Pixel을 사용하는 이유

기존 추적 스크립트(Google Tag Manager, Facebook Pixel 등)는 전체 페이지 접근 권한으로 실행되며, 스토어프론트를 느리게 만들 수 있고, 적절한 동의 없이 데이터를 수집하는 경우가 많습니다. Shopify의 web pixel 프레임워크는 세 가지 문제를 모두 해결합니다: 성능을 위한 샌드박스 실행, 일관성을 위한 표준화된 이벤트, 개인정보 보호 컴플라이언스를 위한 기본 동의 관리.

Web Pixel 작동 방식

Web pixel은 샌드박스된 Web Worker 내부에서 실행됩니다. DOM에 접근하거나, 쿠키를 읽거나, 페이지와 직접 상호작용할 수 없습니다. 대신, 고객이 탐색하고, 장바구니에 아이템을 추가하고, 구매를 완료할 때 Shopify가 발생시키는 표준화된 고객 이벤트 스트림을 구독합니다.

고객 이벤트

Shopify는 표준화된 전자상거래 이벤트의 포괄적인 세트를 발생시킵니다. pixel은 필요한 이벤트를 구독합니다:

이벤트트리거주요 데이터
page_viewed고객이 페이지를 조회URL, 페이지 제목, 리퍼러
product_viewed고객이 제품 페이지를 조회제품 ID, 제목, 가격, 옵션
collection_viewed고객이 컬렉션을 조회컬렉션 ID, 제목
search_submitted고객이 검색을 수행검색 쿼리, 결과 수
product_added_to_cart장바구니에 아이템 추가제품, 옵션, 수량, 장바구니 합계
product_removed_from_cart장바구니에서 아이템 제거제품, 옵션, 수량
cart_viewed고객이 장바구니 페이지를 조회장바구니 내용, 합계
checkout_started고객이 결제를 시작장바구니 내용, 합계, 할인 코드
checkout_address_info_submitted주소 양식 완료국가, 주/도 (PII 없음)
checkout_shipping_info_submitted배송 방법 선택배송 방법, 비용
payment_info_submitted결제 정보 입력결제 수단 유형 (카드 정보 없음)
checkout_completed구매 완료주문 ID, 합계, 라인 아이템, 통화
이벤트 데이터 개인정보

고객 이벤트는 기본적으로 익명화된 데이터를 포함합니다. 이메일 주소와 전체 이름 같은 개인 식별 정보(PII)는 고객이 명시적 동의를 부여한 경우에만 포함됩니다. pixel은 PII 필드에 접근하기 전에 event.data.customer.consent를 확인해야 합니다.

Web Pixel 확장 생성

# Generate a web pixel extension
shopify app generate extension --template web_pixel --name custom-analytics
extensions/custom-analytics/
├── src/
│ └── index.ts
├── shopify.extension.toml
└── package.json

확장 설정

# extensions/custom-analytics/shopify.extension.toml
api_version = "2025-01"

[[extensions]]
type = "web_pixel_extension"
name = "Custom Analytics Pixel"
handle = "custom-analytics"

[extensions.settings]
[[extensions.settings.fields]]
key = "analytics_endpoint"
type = "single_line_text_field"
name = "Analytics Endpoint URL"
description = "The URL where event data will be sent"

[[extensions.settings.fields]]
key = "api_key"
type = "single_line_text_field"
name = "API Key"
description = "Your analytics platform API key"

[[extensions.settings.fields]]
key = "track_search"
type = "boolean"
name = "Track Search Events"
description = "Enable tracking of search queries"

커스텀 Pixel 구현

pixel 진입점은 이벤트를 구독하고 처리합니다. 주요 전자상거래 이벤트를 추적하는 전체 구현입니다:

// extensions/custom-analytics/src/index.ts
import { register } from '@shopify/web-pixels-extension';

register(({ analytics, browser, settings, init }) => {
const endpoint = settings.analytics_endpoint;
const apiKey = settings.api_key;
const trackSearch = settings.track_search === 'true';

// Helper to send events to your analytics backend
async function sendEvent(eventName: string, payload: Record<string, any>) {
try {
await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': apiKey,
},
body: JSON.stringify({
event: eventName,
timestamp: new Date().toISOString(),
shopId: init.data.shop.id,
payload,
}),
// Use keepalive to ensure the request completes
// even if the customer navigates away
keepalive: true,
});
} catch (err) {
// Silently fail -- do not disrupt the customer experience
console.error(`Failed to send ${eventName} event:`, err);
}
}

// Track page views
analytics.subscribe('page_viewed', (event) => {
sendEvent('page_view', {
url: event.data.url,
title: event.data.pageTitle,
referrer: event.data.referrer,
});
});

// Track product views
analytics.subscribe('product_viewed', (event) => {
const product = event.data.productVariant;
sendEvent('product_view', {
productId: product?.product?.id,
productTitle: product?.product?.title,
variantId: product?.id,
variantTitle: product?.title,
price: product?.price?.amount,
currency: product?.price?.currencyCode,
});
});

// Track add to cart
analytics.subscribe('product_added_to_cart', (event) => {
const cartLine = event.data.cartLine;
sendEvent('add_to_cart', {
productId: cartLine?.merchandise?.product?.id,
productTitle: cartLine?.merchandise?.product?.title,
variantId: cartLine?.merchandise?.id,
quantity: cartLine?.quantity,
price: cartLine?.merchandise?.price?.amount,
currency: cartLine?.merchandise?.price?.currencyCode,
});
});

// Track cart removals
analytics.subscribe('product_removed_from_cart', (event) => {
const cartLine = event.data.cartLine;
sendEvent('remove_from_cart', {
productId: cartLine?.merchandise?.product?.id,
variantId: cartLine?.merchandise?.id,
quantity: cartLine?.quantity,
});
});

// Track checkout started
analytics.subscribe('checkout_started', (event) => {
const checkout = event.data.checkout;
sendEvent('begin_checkout', {
totalPrice: checkout?.totalPrice?.amount,
currency: checkout?.totalPrice?.currencyCode,
lineItemCount: checkout?.lineItems?.length,
discountCodes: checkout?.discountApplications?.map(
(d: any) => d.title
),
});
});

// Track purchase completed
analytics.subscribe('checkout_completed', (event) => {
const checkout = event.data.checkout;
sendEvent('purchase', {
orderId: checkout?.order?.id,
totalPrice: checkout?.totalPrice?.amount,
subtotalPrice: checkout?.subtotalPrice?.amount,
totalTax: checkout?.totalTax?.amount,
currency: checkout?.totalPrice?.currencyCode,
lineItems: checkout?.lineItems?.map((item: any) => ({
productId: item.variant?.product?.id,
productTitle: item.variant?.product?.title,
variantId: item.variant?.id,
quantity: item.quantity,
price: item.variant?.price?.amount,
})),
shippingRate: {
price: checkout?.shippingLine?.price?.amount,
},
});
});

// Conditionally track search
if (trackSearch) {
analytics.subscribe('search_submitted', (event) => {
sendEvent('search', {
query: event.data.searchResult?.query,
resultCount: event.data.searchResult?.productVariants?.length ?? 0,
});
});
}

// Track collection views
analytics.subscribe('collection_viewed', (event) => {
sendEvent('collection_view', {
collectionId: event.data.collection?.id,
collectionTitle: event.data.collection?.title,
});
});
});

개인정보 보호 준수 분석

Shopify의 web pixel 프레임워크는 고객 개인정보 동의 시스템과 통합됩니다. pixel은 동의 상태를 준수해야 합니다.

동의 인식 이벤트 처리

// Enhanced pixel with consent checking
register(({ analytics, browser, settings, init }) => {
const endpoint = settings.analytics_endpoint;

// Check initial consent status
let analyticsConsent = init.customerPrivacy?.analyticsProcessingAllowed ?? false;
let marketingConsent = init.customerPrivacy?.marketingAllowed ?? false;

// Listen for consent changes (customer updates preferences)
analytics.subscribe('visitor_consent_collected', (event) => {
analyticsConsent = event.data.analyticsAllowed;
marketingConsent = event.data.marketingAllowed;
});

async function sendEvent(
eventName: string,
payload: Record<string, any>,
requiresMarketingConsent: boolean = false
) {
// Always check consent before sending data
if (!analyticsConsent) return;
if (requiresMarketingConsent && !marketingConsent) return;

await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
event: eventName,
timestamp: new Date().toISOString(),
consent: {
analytics: analyticsConsent,
marketing: marketingConsent,
},
payload,
}),
keepalive: true,
});
}

// Basic analytics events (require analytics consent)
analytics.subscribe('page_viewed', (event) => {
sendEvent('page_view', {
url: event.data.url,
title: event.data.pageTitle,
});
});

// Marketing-related events (require marketing consent)
analytics.subscribe('checkout_completed', (event) => {
// Send conversion data to ad platforms only with marketing consent
sendEvent(
'conversion',
{
orderId: event.data.checkout?.order?.id,
value: event.data.checkout?.totalPrice?.amount,
currency: event.data.checkout?.totalPrice?.currencyCode,
},
true // requires marketing consent
);
});
});
GDPR 및 개인정보 보호 규정

web pixel은 GDPR, CCPA 및 기타 개인정보 보호 규정을 준수해야 합니다. 주요 요구 사항:

  • 동의 없이 PII를 수집하지 마십시오 -- 이벤트 데이터에 포함되어 있더라도
  • 옵트아웃 요청을 존중하십시오 -- 고객이 동의를 철회하면 즉시 데이터 전송을 중단하십시오
  • 데이터 최소화 -- 필요한 것만 수집하십시오. 원시 이벤트 페이로드를 서드파티에 전달하지 마십시오
  • 보존 제한 -- 분석 백엔드가 개인정보 보호 정책 기한에 따라 데이터를 삭제하도록 하십시오

위반 시 Shopify App Store에서 앱이 제거될 수 있으며 잠재적 법적 책임이 발생할 수 있습니다.

서드파티 분석 연동

Google Analytics 4

// GA4 integration via Measurement Protocol
register(({ analytics, settings }) => {
const measurementId = settings.ga4_measurement_id;
const apiSecret = settings.ga4_api_secret;
const GA4_ENDPOINT = `https://www.google-analytics.com/mp/collect?measurement_id=${measurementId}&api_secret=${apiSecret}`;

function sendToGA4(eventName: string, params: Record<string, any>) {
fetch(GA4_ENDPOINT, {
method: 'POST',
body: JSON.stringify({
client_id: generateClientId(), // Generate a privacy-safe client ID
events: [{ name: eventName, params }],
}),
keepalive: true,
});
}

analytics.subscribe('product_viewed', (event) => {
const product = event.data.productVariant;
sendToGA4('view_item', {
currency: product?.price?.currencyCode,
value: parseFloat(product?.price?.amount || '0'),
items: [{
item_id: product?.product?.id,
item_name: product?.product?.title,
item_variant: product?.title,
price: parseFloat(product?.price?.amount || '0'),
}],
});
});

analytics.subscribe('product_added_to_cart', (event) => {
const cartLine = event.data.cartLine;
sendToGA4('add_to_cart', {
currency: cartLine?.merchandise?.price?.currencyCode,
value: parseFloat(cartLine?.merchandise?.price?.amount || '0') * (cartLine?.quantity || 1),
items: [{
item_id: cartLine?.merchandise?.product?.id,
item_name: cartLine?.merchandise?.product?.title,
quantity: cartLine?.quantity,
price: parseFloat(cartLine?.merchandise?.price?.amount || '0'),
}],
});
});

analytics.subscribe('checkout_completed', (event) => {
const checkout = event.data.checkout;
sendToGA4('purchase', {
transaction_id: checkout?.order?.id,
currency: checkout?.totalPrice?.currencyCode,
value: parseFloat(checkout?.totalPrice?.amount || '0'),
tax: parseFloat(checkout?.totalTax?.amount || '0'),
shipping: parseFloat(checkout?.shippingLine?.price?.amount || '0'),
items: checkout?.lineItems?.map((item: any) => ({
item_id: item.variant?.product?.id,
item_name: item.variant?.product?.title,
quantity: item.quantity,
price: parseFloat(item.variant?.price?.amount || '0'),
})),
});
});
});

function generateClientId(): string {
// Generate a random client ID that does not contain PII
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
/[xy]/g,
(c) => {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
}
);
}

Meta Conversions API

// Meta (Facebook) Conversions API integration
register(({ analytics, settings }) => {
const pixelId = settings.meta_pixel_id;
const accessToken = settings.meta_access_token;
const META_ENDPOINT = `https://graph.facebook.com/v18.0/${pixelId}/events`;

function sendToMeta(eventName: string, customData: Record<string, any>) {
fetch(META_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
data: [{
event_name: eventName,
event_time: Math.floor(Date.now() / 1000),
action_source: 'website',
custom_data: customData,
}],
access_token: accessToken,
}),
keepalive: true,
});
}

analytics.subscribe('product_viewed', (event) => {
const product = event.data.productVariant;
sendToMeta('ViewContent', {
content_ids: [product?.product?.id],
content_type: 'product',
content_name: product?.product?.title,
value: parseFloat(product?.price?.amount || '0'),
currency: product?.price?.currencyCode,
});
});

analytics.subscribe('product_added_to_cart', (event) => {
const cartLine = event.data.cartLine;
sendToMeta('AddToCart', {
content_ids: [cartLine?.merchandise?.product?.id],
content_type: 'product',
value: parseFloat(cartLine?.merchandise?.price?.amount || '0'),
currency: cartLine?.merchandise?.price?.currencyCode,
});
});

analytics.subscribe('checkout_completed', (event) => {
const checkout = event.data.checkout;
sendToMeta('Purchase', {
content_ids: checkout?.lineItems?.map(
(item: any) => item.variant?.product?.id
),
content_type: 'product',
value: parseFloat(checkout?.totalPrice?.amount || '0'),
currency: checkout?.totalPrice?.currencyCode,
num_items: checkout?.lineItems?.length,
});
});
});

테스트 및 디버깅

# Start development server with pixel debugging
shopify app dev

# The CLI provides a preview URL with pixel debugging enabled
# Open the browser console to see pixel event logs

디버그 모드

register(({ analytics, settings, init }) => {
const debug = init.data.shop.myshopifyDomain.includes('dev');

function log(eventName: string, data: any) {
if (debug) {
console.log(`[Pixel Debug] ${eventName}:`, JSON.stringify(data, null, 2));
}
}

analytics.subscribe('page_viewed', (event) => {
log('page_viewed', event.data);
// ... normal processing
});
});
개발 환경에서 Pixel 테스트

브라우저의 Network 탭을 사용하여 pixel이 올바른 엔드포인트에 요청을 보내고 있는지 확인하십시오. Shopify의 개발 도구는 페이지에서 발생한 모든 이벤트와 어떤 pixel이 이를 수신했는지를 보여주는 "Customer Events" 디버거도 제공합니다. 개발 스토어에서 ?debug_pixels=true 쿼리 파라미터를 통해 접근할 수 있습니다.

성능 고려 사항

  1. 모든 fetch 요청에 keepalive: true를 사용하십시오 -- 고객이 요청 도중 페이지를 떠나도 이벤트가 전송되도록 합니다
  2. 가능하면 이벤트를 배치 처리하십시오 -- 이벤트당 하나의 요청을 보내는 대신, 이벤트를 버퍼링하고 몇 초마다 배치로 전송하십시오
  3. 페이로드 크기를 최소화하십시오 -- 분석 플랫폼이 필요로 하는 데이터만 포함하십시오
  4. 동기 패턴을 피하십시오 -- pixel의 모든 것이 논블로킹이어야 합니다
  5. 네트워크 실패를 조용히 처리하십시오 -- 추적 실패가 고객 경험에 영향을 미치지 않도록 하십시오
// Event batching implementation
class EventBatcher {
private queue: any[] = [];
private timer: ReturnType<typeof setTimeout> | null = null;
private readonly endpoint: string;
private readonly batchSize: number;
private readonly flushInterval: number;

constructor(endpoint: string, batchSize = 10, flushInterval = 3000) {
this.endpoint = endpoint;
this.batchSize = batchSize;
this.flushInterval = flushInterval;
}

add(event: any) {
this.queue.push(event);

if (this.queue.length >= this.batchSize) {
this.flush();
} else if (!this.timer) {
this.timer = setTimeout(() => this.flush(), this.flushInterval);
}
}

private flush() {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}

if (this.queue.length === 0) return;

const batch = this.queue.splice(0, this.batchSize);

fetch(this.endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ events: batch }),
keepalive: true,
}).catch(() => {
// Re-queue failed events (with a retry limit)
// In production, add exponential backoff
});
}
}
요약

Web pixel 확장은 Shopify 스토어프론트에서 고객 행동을 추적하는 안전하고, 성능이 우수하며, 개인정보 보호를 준수하는 방법을 제공합니다. Shopify의 표준화된 이벤트 시스템을 사용하면 DOM 스크래핑이나 쿠키 추적의 취약성 없이 모든 판매자에 걸쳐 일관된 데이터를 얻을 수 있습니다. 동의 관리와 결합하면 분석 연동이 개인정보 보호 우선 웹에 대비할 수 있습니다.