# Get Started URL: /docs/get-started What Sophi is, how to pick your integration path, and a 5-minute quick start to send your first event. # Get Started with Sophi [#get-started-with-sophi] Sophi is an agentic commerce platform. It learns from your product catalog and your shoppers' behavior, then acts — surfacing the right products, at the right moment, without requiring explicit search queries. There are two integration tracks. Most stores complete both. *** ## Which Path to Start With [#which-path-to-start-with] **Start with Product Integration** if you have an existing product catalog. Sophi needs to know your products before it can recommend them. The feed is an RSS 2.0 / Google Merchant Center compatible XML file — if you already export to Google Shopping, you're most of the way there. **Start with Frontend Integration** if you want to capture user behavior first, or if your catalog is small enough to onboard manually. The two packages are independent: * `@usesophi/sophi-web-sdk` — the Sophi assistant widget * `@usesophi/sophi-web-object` — storefront event tracking You can run them in parallel. *** ## Quick Start (5 Minutes) [#quick-start-5-minutes] This gets your first event to Sophi. You need a Sophi account and your API key from the dashboard. ### Step 1: Install the Web Object Package [#step-1-install-the-web-object-package] ```bash npm install @usesophi/sophi-web-object # or yarn add @usesophi/sophi-web-object ``` ### Step 2: Configure Sophi [#step-2-configure-sophi] Define `window.sophi_object.config` before the Web Object bundle loads — the SDK reads it on startup. Only `apiKey` is required: ```js window.sophi_object = { config: { apiKey: 'YOUR_API_KEY', userId: 'usr-123', }, }; ``` Then load the bundle: ```html ``` See [Configuration](/docs/frontend-integration/web-object/configuration) for every option (`maxRetries`, `debounceMs`, `debug`, `storageConsent`). ### Step 3: Send Your First Events [#step-3-send-your-first-events] After the bundle starts, push storefront context with `SophiTracker.push(...)`. Sophi emits one event per watched key — here `page` produces a `page_view` and `product` a `product_view`: ```js SophiTracker.push({ page: { type: 'Product', url: 'https://store.com/products/blue-dress', }, product: { id: '1627421', name: 'Blue Dress', currency: 'TRY', unit_price: 1200.0, unit_sale_price: 990.0, in_stock: 1, }, }); ``` For SPA route changes, add-to-cart, purchases, and login/logout, see [Integration patterns](/docs/frontend-integration/web-object/integration-patterns). Open your Sophi dashboard and navigate to the Events feed. Events appear within a few seconds of being sent. If you see your `page_view` and `product_view` there, the integration is working. *** ## Use with your AI agent [#use-with-your-ai-agent] Give your AI coding agent (Claude Code, Cursor, Windsurf) expert Sophi knowledge in one command. The skill fetches the live docs at task time so it never goes stale. [Install the Sophi skill →](/docs/use-with-ai) *** ## Next Steps [#next-steps] # Sophi Documentation URL: /docs Build with Sophi — agentic commerce for your store. # Sophi Developer Docs [#sophi-developer-docs] Sophi is an agentic commerce platform. It learns from your product catalog and your shoppers' behavior, then acts — surfacing the right products at the right moment, without requiring explicit search queries. There are two integration tracks. Most stores complete both. # Cookie Consent Management URL: /docs/frontend-integration/cookie-consent How to keep Sophi usable from the first page view while respecting GDPR/KVKK cookie rules. This page explains how to keep Sophi usable from the first page view while respecting GDPR/KVKK cookie rules. Recommended approach for most storefronts: 1. Load Sophi on all pages (including before consent), often via [SDK GTM Integration](/docs/frontend-integration/sdk/gtm-integration). 2. Start in memory-only mode (`storageConsent: false`). 3. Upgrade to persistent mode immediately when cookie consent is granted. This gives visitors an instant chat experience and keeps tracking/storage behavior aligned with your consent policy. ## Pre-consent behavior (memory-only chat) [#pre-consent-behavior-memory-only-chat] Before consent is granted: * **No cookies or `localStorage` are written or read.** The chat session lives only in memory, so the visitor starts **fresh on every page load**. * The chat works fully as a **guest** — no `userId` is sent to the backend, so the session is never linked to a known user. * The **Web Object** analytics SDK stays **completely passive**: no session is created and **no events are sent** until consent is granted. When consent is granted, the SDK upgrades in place: it persists the current in-memory session to cookies and (for logged-in users) merges the guest session into the user account. The **chat** (`sophi-web-sdk`) runs anonymously as a guest because it is a service the visitor explicitly requested. The **Web Object** analytics SDK (`sophi-web-object`) sends **nothing** until consent — tracking requires consent. ## `storageConsent` flag [#storageconsent-flag] A single flag controls consent-aware behavior end-to-end. It defaults to `true` (the existing persistent behavior), so memory-only pre-consent mode is strictly **opt-in** and existing integrations are unaffected. | Surface | How to set it | | ----------------------------------------- | ----------------------------------------------------------- | | Widget Launcher | `consent: { storage: false }` in `window.sophiWidgetConfig` | | Web SDK (`SophiWidget.init`) | `storageConsent: false` | | Web Object (`window.sophi_object.config`) | `storageConsent: false` | ## Runtime consent updates [#runtime-consent-updates] When the visitor accepts cookies, call the matching method. The open chat is upgraded in place — the conversation is not lost. ```js // e.g. inside your cookie banner's "Accept" handler window.sophiWidget.setConsent(true); ``` ```js widget.setStorageConsent(true); ``` ```js window.SophiTracker.setConsent(true); ``` Calling the same method with `false` withdraws consent at runtime: cookies are cleared and the SDK falls back to memory-only. ## Runtime upgrade flow [#runtime-upgrade-flow] The sequence below shows what happens when a logged-in visitor accepts cookies while the chat is open: * For **guest** visitors, only the cookies are written — no merge. * For **logged-in** visitors, the existing guest session is merged into the user via Sophi's standard endpoint. No extra backend work is required. ## GTM-first integration guidance [#gtm-first-integration-guidance] If you use GTM, this is the preferred rollout pattern: * Load the widget on all pages through GTM. * Initialize with `storageConsent: false` until consent is granted. * On banner accept, call `setConsent(true)` / `setStorageConsent(true)`. * Keep Web Object passive until consent, then enable it with the same consent signal. If your policy requires loading nothing until consent, use a strict consent-gated setup from [SDK GTM Integration](/docs/frontend-integration/sdk/gtm-integration). ## Why this structure [#why-this-structure] Splitting cookie consent handling into its own section improves usability for integration teams: * Product and legal teams can review policy impact in one place. * Frontend teams can implement a single consent signal across SDK surfaces. * GTM owners can follow a clear activation path without reading low-level SDK details first. ## Related docs [#related-docs] * [SDK Initialization](/docs/frontend-integration/sdk/initialization) — `storageConsent` option * [SDK Methods](/docs/frontend-integration/sdk/methods) — `widget.setStorageConsent(granted)` * [SDK GTM Integration](/docs/frontend-integration/sdk/gtm-integration) — load on all pages, upgrade on consent * [Web Object Configuration](/docs/frontend-integration/web-object/configuration) — passive without consent * [Web Object SophiTracker API](/docs/frontend-integration/web-object/sophi-tracker) — `SophiTracker.setConsent(granted)` # Frontend Integration URL: /docs/frontend-integration Embed the Sophi assistant widget, track storefront events, and manage cookie consent with framework guides for React and Angular. Frontend integration covers two npm packages plus framework-specific guides: * **[@usesophi/sophi-web-sdk](/docs/frontend-integration/sdk)** — embed the Sophi assistant widget (`SophiWidget`) and handle widget events. * **[@usesophi/sophi-web-object](/docs/frontend-integration/web-object)** — framework-agnostic e-commerce tracking via `window.sophi_object` and `SophiTracker`, with events delivered to Sophi over HTTPS. * **Framework guides** — patterns for React, Angular, and more. Looking to run Sophi **before** the visitor accepts cookies? See **[Cookie Consent Management](/docs/frontend-integration/cookie-consent)**. # Sophi Ürün Feed Rehberi / Product Feed Guide URL: /docs/product-integration/feed-guide Complete specification for the Sophi product XML feed — required fields, variant grouping rules, product attributes, and common mistakes. *** ## 1. Genel Bakış / Overview [#1-genel-bakış--overview] Sophi, e-ticaret sitenizdeki ürün bilgilerini yapılandırılmış bir XML feed aracılığıyla alır. Bu feed, ürünlerinizin özelliklerini, varyant bilgilerini, fiyatlarını ve stok durumlarını içerir. Bu rehber, feed'in beklenen yapısını ve içermesi gereken alanları açıklar. [Sample Feed XML](/docs/product-integration/sample-feed) sayfasında canlı bir örnek ve [raw XML dosyası](/product-integration/sample_feed.xml) bulunmaktadır. > Sophi receives your product data through a structured XML feed. This guide explains the expected format, required fields, and common pitfalls. See [Sample Feed XML](/docs/product-integration/sample-feed) and [raw XML](/product-integration/sample_feed.xml) for a working example. *** ## 2. Feed Formatı / Feed Format [#2-feed-formatı--feed-format] * **Format:** RSS 2.0, Google Merchant Center uyumlu * **Encoding:** UTF-8 * **Namespace:** `xmlns:g="http://base.google.com/ns/1.0"` * **Kök yapı / Root structure:** ```xml Product Feed https://www.siteniz.com Ürün kataloğu ... ``` Her `` bir ürün **varyantını** temsil eder (benzersiz beden + renk kombinasyonu). Aynı ürünün tüm varyantları aynı `item_group_id` değerini paylaşır. *** ## 3. Zorunlu Alanlar / Required Fields [#3-zorunlu-alanlar--required-fields] | Alan / Field | Açıklama | Format | Örnek | | ------------------------- | ---------------------------------------------- | -------------- | --------------------------------- | | `g:id` | Varyanta özgü benzersiz ID (SKU veya barkod) | Metin | `BLZ-001-SYA-S` | | `g:title` | Ürün başlığı | Metin | `Marka Desenli Bluz Siyah` | | `g:description` | Detaylı ürün açıklaması (kumaş, kesim, stil) | CDATA metin | `Desenli bluz. %85 polyester...` | | `g:link` | Ürün sayfası URL | URL | `https://www.site.com/urun-p-123` | | `g:image_link` | Ana ürün görseli | URL | `https://cdn.site.com/img/01.jpg` | | `g:additional_image_link` | Ek görseller (her görsel için ayrı etiket) | URL | `https://cdn.site.com/img/02.jpg` | | `g:availability` | Stok durumu | Sabit değer | `in_stock` | | `g:brand` | Marka adı | Metin | `VAKKO` | | `g:color` | Bu varyantın rengi | Metin | `Siyah` | | `g:condition` | Ürün durumu | Sabit değer | `new` | | `g:gender` | Cinsiyet | Sabit değer | `female` | | `g:gtin` | Barkod (GTIN / EAN) | 13 haneli sayı | `8680000000001` | | `g:item_group_id` | Ürün grubu ID (tüm varyantlar için aynı) | Metin | `BLZ-001` | | `g:price` | Liste fiyatı | `0.00 TRY` | `1990.00 TRY` | | `g:sale_price` | İndirimli fiyat (indirim yoksa price ile aynı) | `0.00 TRY` | `1490.00 TRY` | | `g:product_type` | Kategori yolu | CDATA metin | `Kadın > Giyim > Bluz` | | `g:size` | Bu varyantın bedeni | Metin | `S`, `M`, `38`, `39` | **Sabit değer seçenekleri / Allowed tokens:** | Alan | Geçerli değerler | | ---------------- | --------------------------------------------------------- | | `g:availability` | `in_stock` \| `out_of_stock` \| `preorder` \| `backorder` | | `g:condition` | `new` \| `refurbished` \| `used` | | `g:gender` | `male` \| `female` \| `unisex` | *** ## 4. Detaylı Alanlar / Detailed Fields [#4-detaylı-alanlar--detailed-fields] ### `g:material` - Malzeme Bileşimi [#gmaterial---malzeme-bileşimi] Kumaş veya malzeme bileşimini yüzde oranlarıyla belirtin. ```xml %85 Polyester, %10 Elastan, %5 Metalik Elyaf ``` Birden fazla malzeme bileşeni varsa, tek bir etiket içinde virgül ile ayırabilir veya her biri için ayrı etiket kullanabilirsiniz: ```xml %85 Polyester, %10 Elastan, %5 Metalik Elyaf %85 Polyester %10 Elastan %5 Metalik Elyaf ``` ### `g:product_detail` - Ürün Özellikleri [#gproduct_detail---ürün-özellikleri] Yapılandırılmış ürün özelliklerini aktarmak için kullanılır. Her `product_detail` bloğu üç alan içerir: ```xml Bölüm Adı Özellik Adı Değer ``` Bir ürün için istediğiniz kadar `product_detail` bloğu ekleyebilirsiniz. **Sisteminizde bulunan TÜM ürün özelliklerini paylaşın** - ne kadar fazla veri olursa müşteri deneyimi o kadar iyi olur. *** ## 5. Varyant Yönetimi / Variant Management [#5-varyant-yönetimi--variant-management] Bu, feed'in en kritik bölümüdür. Varyant gruplama doğru yapılmazsa, Sophi ürünleri doğru şekilde eşleştiremez. ### Temel Kural [#temel-kural] **Bir `item_group_id` = Bir ürün.** Aynı ürünün tüm beden ve renk kombinasyonları aynı `item_group_id` değerini paylaşır. ### Örnek: Doğru Gruplama [#örnek-doğru-gruplama] Bir bluzun 2 rengi (Siyah, Beyaz) ve 3 bedeni (S, M, L) varsa, toplam 6 varyant olmalıdır ve **hepsi aynı** `item_group_id`'yi paylaşır: ``` item_group_id = "BLZ-001" ├── BLZ-001-SYA-S (Siyah, S) ├── BLZ-001-SYA-M (Siyah, M) ├── BLZ-001-SYA-L (Siyah, L) ├── BLZ-001-BYZ-S (Beyaz, S) ├── BLZ-001-BYZ-M (Beyaz, M) └── BLZ-001-BYZ-L (Beyaz, L) ``` Farklı bir ürün (örneğin bir sneaker) **farklı** bir `item_group_id` alır: ``` item_group_id = "SNK-042" ├── SNK-042-SYA-39 (Siyah, 39) ├── SNK-042-SYA-40 (Siyah, 40) └── SNK-042-SYA-41 (Siyah, 41) ``` ### YANLIŞ: Renkleri Ayrı Grup Yapmak [#yanliş-renkleri-ayrı-grup-yapmak] Aşağıdaki yapı **HATALIDIR**. Her renk için farklı `item_group_id` kullanmayın: ``` YANLIŞ: item_group_id = "BLZ-001-SYA" <-- Siyah için ayrı grup ├── BLZ-001-SYA-S ├── BLZ-001-SYA-M └── BLZ-001-SYA-L item_group_id = "BLZ-001-BYZ" <-- Beyaz için ayrı grup ├── BLZ-001-BYZ-S ├── BLZ-001-BYZ-M └── BLZ-001-BYZ-L ``` Bu yapıda Sophi aynı ürünün iki farklı rengi olduğunu anlayamaz ve bunları **iki ayrı ürün** olarak görür. ### DOĞRU: Tüm Renkler ve Bedenler Tek Grupta [#doğru-tüm-renkler-ve-bedenler-tek-grupta] ``` DOĞRU: item_group_id = "BLZ-001" <-- Tek grup, tüm varyantlar içinde ├── BLZ-001-SYA-S (color=Siyah, size=S) ├── BLZ-001-SYA-M (color=Siyah, size=M) ├── BLZ-001-SYA-L (color=Siyah, size=L) ├── BLZ-001-BYZ-S (color=Beyaz, size=S) ├── BLZ-001-BYZ-M (color=Beyaz, size=M) └── BLZ-001-BYZ-L (color=Beyaz, size=L) ``` ### Varyantlar Arası Farklar [#varyantlar-arası-farklar] Aynı `item_group_id`'ye sahip varyantlar arasında değişen ve değişmeyen alanlar: | Varyantlar arası DEĞİŞEN | Varyantlar arası AYNI KALAN | | ------------------------------------ | --------------------------- | | `g:id` (benzersiz SKU) | `g:item_group_id` | | `g:color` | `g:brand` | | `g:size` | `g:product_type` | | `g:gtin` (benzersiz barkod) | `g:material` | | `g:image_link` (renk bazlı) | `g:product_detail` blokları | | `g:availability` (stok bazlı) | `g:gender` | | `g:price` / `g:sale_price` (nadiren) | `g:condition` | *** ## 6. Ürün Özellikleri / Product Attributes [#6-ürün-özellikleri--product-attributes] `product_detail` blokları ile yapılandırılmış ürün özelliklerini aktarabilirsiniz. Aşağıda kategori bazında örnek özellikler verilmiştir. ### Giyim / Apparel [#giyim--apparel] | section\_name | attribute\_name | Örnek değerler | | ---------------- | --------------- | ----------------------------------------------- | | Ürün Özellikleri | Ürün Grubu | Bluz, Gömlek, Pantolon, Ceket, Elbise | | Ürün Özellikleri | Kumaş Tipi | Örme, Dokuma, Denim, Triko | | Ürün Özellikleri | Kesim Tipi | Standart Kalıp, Slim Fit, Oversize, Regular Fit | | Ürün Özellikleri | Yaka Tipi | O Yaka, V Yaka, Polo Yaka, Gömlek Yaka | | Ürün Özellikleri | Kol Tipi | Kısa Kollu, Uzun Kollu, Kolsuz, 3/4 Kollu | | Ürün Özellikleri | Desen | Düz, Desenli, Çizgili, Kareli, Çiçek Desenli | | Ürün Özellikleri | Boy | Mini, Midi, Maxi | | Ürün Özellikleri | Astar Bilgisi | Astarlı, Astarsız | | Ürün Özellikleri | Cep Bilgisi | Cepli, Cepsiz | | Ürün Özellikleri | Kapama Tipi | Düğmeli, Fermuar, Bağcıklı | | Ürün Özellikleri | Yaşam Tarzı | Casual, Spor, Klasik, Özel Gün | ### Ayakkabı / Shoes [#ayakkabı--shoes] | section\_name | attribute\_name | Örnek değerler | | ---------------- | ---------------- | ------------------------------------ | | Ürün Özellikleri | Ürün Grubu | Sneaker, Bot, Sandalet, Topuklu | | Ürün Özellikleri | Dış Materyal | Doğal Deri, Suni Deri, Tekstil, Süet | | Ürün Özellikleri | İç Materyal | Tekstil, Deri, Suni Deri | | Ürün Özellikleri | Taban Tipi | Kauçuk, EVA, Deri, TPU | | Ürün Özellikleri | Topuk Yüksekliği | 3 cm, 5 cm, 7 cm, 10 cm | | Ürün Özellikleri | Kapama Tipi | Bağcıklı, Fermuar, Tokalı, Slip-on | ### Ev & Yaşam / Home & Living [#ev--yaşam--home--living] | section\_name | attribute\_name | Örnek değerler | | ---------------- | --------------- | ------------------------------------- | | Ürün Özellikleri | Ürün Grubu | Nevresim, Havlu, Bardak, Tabak | | Ürün Özellikleri | Boyut | 50x70 cm, 200x220 cm, 250 ml | | Ürün Özellikleri | Malzeme | Pamuk, Porselen, Cam, Paslanmaz Çelik | | Ürün Özellikleri | İplik Sayısı | 300 TC, 400 TC | ### Kozmetik / Cosmetics [#kozmetik--cosmetics] | section\_name | attribute\_name | Örnek değerler | | ---------------- | --------------- | ---------------------------- | | Ürün Özellikleri | Ürün Grubu | Parfüm, Ruj, Fondöten, Serum | | Ürün Özellikleri | Hacim | 50 ml, 100 ml, 200 ml | | Ürün Özellikleri | Cilt Tipi | Normal, Kuru, Yağlı, Karma | | Ürün Özellikleri | Koku Ailesi | Çiçeksi, Odunsu, Oryantal | ### Genel / Common to All [#genel--common-to-all] | section\_name | attribute\_name | Açıklama | | ---------------- | --------------- | --------------------------------------------------- | | Ürün Özellikleri | Ürün Grubu | **Her kategoride bulunmalı** - ürün tipini tanımlar | | Ürün Özellikleri | Menşei | Üretim ülkesi (Türkiye, İtalya, vb.) | | Ürün Özellikleri | Sezon | Koleksiyon sezonu (SS 2025, FW 2025, vb.) | | Ürün Özellikleri | Koleksiyon | Koleksiyon adı | > **ÖNEMLİ:** Yukarıdaki listeler örnektir. Sisteminizde bulunan her özellik için bir `product_detail` bloğu ekleyin. Sınır yoktur. *** ## 7. Bakım Bilgileri / Care Instructions [#7-bakım-bilgileri--care-instructions] Bakım talimatlarını `product_detail` blokları ile `section_name = "Bakım"` altında aktarın. ```xml Bakım Yıkama 30 derecede makinede yıkanabilir Bakım Ağartma Ağartıcı kullanılmaz Bakım Ütüleme Düşük ısıda ütülenebilir Bakım Kuru Temizleme Kuru temizleme yapılabilir (P) Bakım Kurutma Tamburlu makinede kurutulmaz ``` Bakım alanları tekstil ürünlerinde (giyim, ev tekstili) özellikle önemlidir. *** ## 8. Fiyatlandırma / Pricing [#8-fiyatlandırma--pricing] | Alan | Açıklama | Format | Örnek | | -------------- | ---------------------------- | ---------- | ------------- | | `g:price` | Liste fiyatı (etiket fiyatı) | `0.00 TRY` | `1990.00 TRY` | | `g:sale_price` | İndirimli fiyat | `0.00 TRY` | `1490.00 TRY` | * Fiyat formatı: rakam (2 ondalık) + boşluk + para birimi kodu * İndirim yoksa `sale_price` değeri `price` ile aynı olmalıdır * Her iki alan da **zorunludur** ```xml 1990.00 TRY 1490.00 TRY 1990.00 TRY 1990.00 TRY ``` *** ## 9. Stok Durumu / Availability [#9-stok-durumu--availability] `g:availability` alanı aşağıdaki değerlerden birini **tam olarak** içermelidir: | Değer | Anlamı | | -------------- | ----------------------------------- | | `in_stock` | Stokta var, sevkiyata hazır | | `out_of_stock` | Stokta yok | | `preorder` | Ön sipariş alınabilir | | `backorder` | Sipariş alınabilir, stok bekleniyor | ```xml in_stock ``` > Not: Stokta olmayan ürünleri (`out_of_stock`) feed'e dahil etmek zorunlu değildir, ancak dahil edilmeleri tercih edilir. *** ## 10. Sık Yapılan Hatalar / Common Mistakes [#10-sık-yapılan-hatalar--common-mistakes] Aşağıdaki hatalar gerçek feed analizlerinde tespit edilmiştir. Lütfen feed'inizi oluşturmadan önce bu listeyi kontrol edin. ### 10.1 Renklerin Ayrı Grup Olarak Gönderilmesi [#101-renklerin-ayrı-grup-olarak-gönderilmesi] **En sık karşılaşılan hata.** Her renk için farklı `item_group_id` kullanılması, Sophi'nin aynı ürünün farklı renklerini birleştirmesini engeller. ```xml ABC-123-SIYAH ABC-123-BEYAZ ABC-123 ``` ### 10.2 `color` ve `size` Etiketlerinin Eksik Olması [#102-color-ve-size-etiketlerinin-eksik-olması] Her varyant için `g:color` ve `g:size` etiketleri **ayrı alanlar** olarak bulunmalıdır. Renk veya beden bilgisini sadece başlıkta veya açıklamada belirtmek yeterli değildir. ```xml Marka Bluz Siyah S Marka Bluz Siyah Siyah S ``` ### 10.3 Açıklamalarda HTML Kullanımı [#103-açıklamalarda-html-kullanımı] `g:description` ve diğer alanlarda HTML etiketleri (``, `
`, `
`, inline CSS vb.) **olmamalıdır**. Düz metin veya CDATA kullanın. ```xml Güzel bir
ürün
``` ### 10.4 `product_detail` Bloklarının Eksik Olması [#104-product_detail-bloklarının-eksik-olması] Sisteminizde ürün özellikleri (kumaş tipi, kesim, yaka, desen vb.) mevcut olduğu halde bunların `product_detail` blokları ile aktarılmaması. Bu verileri açıklama alanına gömmeyin, yapılandırılmış olarak gönderin. ```xml Örme kumaş, O yaka, kısa kollu bluz Ürün Özellikleri Kumaş Tipi Örme Ürün Özellikleri Yaka Tipi O Yaka ``` ### 10.5 Malzeme Bilgisinin Eksik Olması [#105-malzeme-bilgisinin-eksik-olması] `g:material` alanı özellikle tekstil ve deri ürünlerinde kritik öneme sahiptir. Malzeme bileşimini yüzde oranlarıyla aktarın. ```xml %85 Polyester, %10 Elastan, %5 Metalik Elyaf ``` ### 10.6 Belirsiz Beden Değerleri [#106-belirsiz-beden-değerleri] "40" gibi bir beden değeri giysi, ayakkabı veya yaş grubu olabilir. Mümkünse beden sistemi hakkında ek bilgi verin veya `product_type` kategorisinin beden tipini anlaşılır kılmasını sağlayın. ### 10.7 Boş veya Placeholder Değerler [#107-boş-veya-placeholder-değerler] Gerçek veri olmayan placeholder değerlerden kaçının: * `%100 Diğer` (bilinmeyen malzeme yerine) * `Çok Renkli` (gerçek renk yerine) * Boş etiketler (``) *** ## 11. Dosyalar / Files [#11-dosyalar--files] Bu klasördeki dosyalar: | Dosya | Açıklama | | -------------------------------------------------------- | ------------------------------------------------------------------------ | | `feed-guide.md` | Bu rehber dosyası | | [sample-feed.md](/docs/product-integration/sample-feed) | Dokümantasyon içinde görüntülenebilir XML örneği | | [sample\_feed.xml](/product-integration/sample_feed.xml) | Raw örnek XML feed (2 ürün, 4 varyant, tüm alanlar ve açıklamalar dahil) | `sample_feed.xml` dosyasında: * **Ürün 1** (Bluz, `item_group_id = BLZ-001`): 3 varyant (Siyah S, Siyah M, Beyaz S) - çoklu varyant örneği * **Ürün 2** (Sneaker, `item_group_id = SNK-042`): 1 varyant (Siyah 39) - farklı ürün örneği, ayakkabıya özel özelliklerle Bu iki ürün birlikte, `item_group_id`'nin nasıl çalıştığını gösterir: aynı ürünün varyantları aynı grupta, farklı ürünler farklı gruplarda. # Product Integration URL: /docs/product-integration Connect your product catalog to Sophi via a structured XML feed. Covers feed format, field reference, variant rules, and a sample feed. Sophi ingests your store's product data through a structured XML feed (RSS 2.0, Google Merchant Center compatible). Once the feed is live, Sophi can match products to user sessions and power agentic recommendations. This track covers everything you need to build and validate your feed. # Sample Feed XML URL: /docs/product-integration/sample-feed A working XML feed example with 2 products and 4 variants, covering all required fields, variant grouping, product attributes, and care instructions. A working reference feed you can validate against. It contains 2 products (4 variants total) with all required and optional fields annotated. * [Download raw XML](/product-integration/sample_feed.xml) * [Back to Product Integration](/docs/product-integration) ```xml Product Feed https://www.example.com Product catalog with detailed attributes BLZ-001-SYA-S Marka Desenli Bluz Siyah https://www.example.com/kadin/bluz/desenli-bluz-siyah-p-BLZ001 https://cdn.example.com/images/blz-001-siyah-01.jpg https://cdn.example.com/images/blz-001-siyah-02.jpg https://cdn.example.com/images/blz-001-siyah-03.jpg https://cdn.example.com/images/blz-001-siyah-04.jpg in_stock MARKA ADI Siyah new female 8680000000001 BLZ-001 1990.00 TRY 1490.00 TRY Giyim > Bluz]]> S %85 Polyester, %10 Elastan, %5 Metalik Elyaf Urun Ozellikleri Kumas Tipi Orme Urun Ozellikleri Kesim Tipi Standart Kalip Urun Ozellikleri Yaka Tipi O Yaka Urun Ozellikleri Kol Tipi Kisa Kollu Urun Ozellikleri Desen Desenli Urun Ozellikleri Yasam Tarzi Casual Urun Ozellikleri Urun Grubu Bluz Bakim Yikama Makinede yikanmaz Bakim Agartma Agartici kullanilmaz Bakim Utuleme Utulenmez Bakim Kuru Temizleme Kuru temizleme yapilabilir (P) Bakim Kurutma Tamburlu makinede kurutulmaz BLZ-001-SYA-M Marka Desenli Bluz Siyah https://www.example.com/kadin/bluz/desenli-bluz-siyah-p-BLZ001 https://cdn.example.com/images/blz-001-siyah-01.jpg https://cdn.example.com/images/blz-001-siyah-02.jpg https://cdn.example.com/images/blz-001-siyah-03.jpg https://cdn.example.com/images/blz-001-siyah-04.jpg out_of_stock MARKA ADI Siyah new female 8680000000002 BLZ-001 1990.00 TRY 1490.00 TRY Giyim > Bluz]]> M %85 Polyester, %10 Elastan, %5 Metalik Elyaf Urun Ozellikleri Kumas Tipi Orme Urun Ozellikleri Kesim Tipi Standart Kalip Urun Ozellikleri Yaka Tipi O Yaka Urun Ozellikleri Kol Tipi Kisa Kollu Urun Ozellikleri Desen Desenli Urun Ozellikleri Yasam Tarzi Casual Urun Ozellikleri Urun Grubu Bluz Bakim Yikama Makinede yikanmaz Bakim Agartma Agartici kullanilmaz Bakim Utuleme Utulenmez Bakim Kuru Temizleme Kuru temizleme yapilabilir (P) Bakim Kurutma Tamburlu makinede kurutulmaz BLZ-001-BYZ-S Marka Desenli Bluz Beyaz https://www.example.com/kadin/bluz/desenli-bluz-beyaz-p-BLZ001 https://cdn.example.com/images/blz-001-beyaz-01.jpg https://cdn.example.com/images/blz-001-beyaz-02.jpg https://cdn.example.com/images/blz-001-beyaz-03.jpg in_stock MARKA ADI Beyaz new female 8680000000003 BLZ-001 1990.00 TRY 1990.00 TRY Giyim > Bluz]]> S %85 Polyester, %10 Elastan, %5 Metalik Elyaf Urun Ozellikleri Kumas Tipi Orme Urun Ozellikleri Kesim Tipi Standart Kalip Urun Ozellikleri Yaka Tipi O Yaka Urun Ozellikleri Kol Tipi Kisa Kollu Urun Ozellikleri Desen Desenli Urun Ozellikleri Yasam Tarzi Casual Urun Ozellikleri Urun Grubu Bluz Bakim Yikama Makinede yikanmaz Bakim Agartma Agartici kullanilmaz Bakim Utuleme Utulenmez Bakim Kuru Temizleme Kuru temizleme yapilabilir (P) Bakim Kurutma Tamburlu makinede kurutulmaz SNK-042-SYA-39 Marka Deri Sneaker Siyah https://www.example.com/erkek/ayakkabi/deri-sneaker-siyah-p-SNK042 https://cdn.example.com/images/snk-042-siyah-01.jpg https://cdn.example.com/images/snk-042-siyah-02.jpg https://cdn.example.com/images/snk-042-siyah-03.jpg in_stock MARKA ADI Siyah new male 8680000000010 SNK-042 3490.00 TRY 2790.00 TRY Ayakkabi > Sneaker]]> 39 %100 Dogal Deri Urun Ozellikleri Urun Grubu Sneaker Urun Ozellikleri Dis Materyal Dogal Deri Urun Ozellikleri Ic Materyal Tekstil Urun Ozellikleri Taban Tipi Kaucuk Urun Ozellikleri Kapama Tipi Bagcikli Urun Ozellikleri Topuk Yuksekligi 3 cm Urun Ozellikleri Yasam Tarzi Casual Urun Ozellikleri Mensei Turkiye Urun Ozellikleri Sezon SS 2025 ``` # AI Coding Agents URL: /docs/use-with-ai Install the Sophi skill so your AI coding agent — Codex, Claude Code, Cursor, Windsurf — integrates Sophi into your storefront for you, reading the live docs as it works. Point your AI coding agent at Sophi and let it do the integration. Install the **Sophi Agent Skill** and tools like **Codex, Claude Code, Cursor, or Windsurf** can wire Sophi into your storefront for you — reading the live Sophi docs at task time (so the code is always current) and writing the product-feed, Web SDK, and Web Object integration against your codebase. The skill points your agent at `https://docs.usesophi.com/llms-full.txt` (a single-file bundle of all Sophi docs) and the per-page `.md` endpoints. The agent reads the live content on demand instead of relying on training-data memory, then writes the integration. ## Install for Claude Code [#install-for-claude-code] Run this one-liner in your terminal. It downloads the skill into the standard Claude Code skills directory. ```bash mkdir -p ~/.claude/skills/sophi && curl -fsSL https://docs.usesophi.com/skill.md -o ~/.claude/skills/sophi/SKILL.md ``` After installation, Claude Code picks up the skill automatically. Ask it to "integrate Sophi" and it fetches the live docs and writes the code. ## Manual download [#manual-download] Prefer to manage the file yourself? Download it directly: ## Works with any coding agent [#works-with-any-coding-agent] | Agent | Where to put it | | --------------- | ------------------------------------------------------------------------------------------------------------------------ | | **Claude Code** | `~/.claude/skills/sophi/SKILL.md` (global) or `/.claude/skills/sophi/SKILL.md` (project-local) | | **Codex** | `AGENTS.md` at your repo root (Codex reads it automatically) | | **Cursor** | `.cursor/rules/sophi.md` (or `.cursorrules`) | | **Windsurf** | `.windsurf/rules/sophi.md` | | **Any agent** | Point it at `https://docs.usesophi.com/llms-full.txt`, or paste the page index from `https://docs.usesophi.com/llms.txt` | ## Per-page AI actions [#per-page-ai-actions] Every page in these docs has **Copy Markdown** and **Open** actions in the top-right corner: * **Copy Markdown** copies the raw Markdown of the page for pasting into a chat. * **Open** opens the current page in ChatGPT or Claude with the full page text as context. Any page is also readable as Markdown by appending `.md` to its URL: ``` https://docs.usesophi.com/docs/frontend-integration/sdk/installation.md ``` ## LLM-friendly endpoints [#llm-friendly-endpoints] | Endpoint | Contents | | --------------------------------------------------- | ------------------------------------------------------------------------- | | `https://docs.usesophi.com/llms-full.txt` | All Sophi docs concatenated — ideal for one-shot context injection | | `https://docs.usesophi.com/llms.txt` | Page index with titles and URLs — ideal for retrieval-augmented workflows | | `https://docs.usesophi.com/.md` | Any individual page as clean Markdown | | `https://docs.usesophi.com/skill.md` · `/AGENTS.md` | The installable agent skill | # Angular Integration URL: /docs/frontend-integration/frameworks/angular Angular integration guide with basic (full-page and modal) and advanced widget methods for Sophi. This page is organized in two levels: * **Basic setup**: copy-paste examples for full-page and modal usage * **Advanced setup**: optional runtime controls (`sendToggleHistory`, `sendProductReply`, `triggerProductReply`) For GTM script loading and script-load failures, use [SDK GTM troubleshooting](/docs/frontend-integration/sdk/gtm-integration#troubleshooting). ## Required in every integration [#required-in-every-integration] No matter which UI pattern you use (full page or modal), implement all three: 1. Handle `add_to_cart` 2. Handle `send_to_checkout` 3. Call `widget.updateUserCart(cart)` when cart changes ## Basic setup [#basic-setup] ## 1) Access object from `window` [#1-access-object-from-window] ```ts const SophiWidgetClass = (window as any).SophiWebSDK?.SophiWidget; if (!SophiWidgetClass) { throw new Error("Sophi SDK not loaded yet"); } ``` ## 2) Initialize widget [#2-initialize-widget] ```ts const widget = new SophiWidgetClass(); await widget.init({ apiKey: "YOUR_SOPHI_API_KEY", container: "#sophi-widget-container", environment: "production", }); ``` ## 3) Full-page minimal example [#3-full-page-minimal-example] ```ts import { AfterViewInit, Component, Input, OnChanges, OnDestroy, SimpleChanges } from "@angular/core"; import { Router } from "@angular/router"; type CartItem = { productId: string; variantId?: string; quantity: number }; @Component({ selector: "app-sophi-full-page", template: `
`, }) export class SophiFullPageComponent implements AfterViewInit, OnChanges, OnDestroy { @Input() cartId = ""; @Input() cartItems: CartItem[] = []; private widget: any = null; constructor(private readonly router: Router) {} async ngAfterViewInit(): Promise { const SophiWidgetClass = (window as any).SophiWebSDK?.SophiWidget; if (!SophiWidgetClass) return; this.widget = new SophiWidgetClass(); await this.widget.init({ apiKey: "YOUR_SOPHI_API_KEY", container: "#sophi-widget-container", environment: "production", width: "100%", height: "100%", }); this.widget.on("add_to_cart", async (data: { products: Array<{ productId: string; variantId?: string }> }) => { for (const product of data.products) { await addToCartInYourApp(product.productId, product.variantId); } }); this.widget.on("send_to_checkout", () => { this.router.navigateByUrl("/checkout"); }); this.syncCart(); } ngOnChanges(changes: SimpleChanges): void { if (!this.widget) return; if (changes["cartId"] || changes["cartItems"]) { this.syncCart(); } } private syncCart(): void { this.widget.updateUserCart({ cartId: this.cartId, items: this.cartItems, }); } async ngOnDestroy(): Promise { await this.widget?.destroy?.(); this.widget = null; } } ``` ## 4) Modal minimal example [#4-modal-minimal-example] ```ts import { Component, Input, OnChanges, OnDestroy, SimpleChanges } from "@angular/core"; import { Router } from "@angular/router"; @Component({ selector: "app-sophi-modal", template: `
`, }) export class SophiModalComponent implements OnChanges, OnDestroy { @Input() cartId = ""; @Input() cartItems: Array<{ productId: string; variantId?: string; quantity: number }> = []; isOpen = false; private widget: any = null; constructor(private readonly router: Router) {} async open(): Promise { this.isOpen = true; if (this.widget) return; const SophiWidgetClass = (window as any).SophiWebSDK?.SophiWidget; if (!SophiWidgetClass) return; this.widget = new SophiWidgetClass(); await this.widget.init({ apiKey: "YOUR_SOPHI_API_KEY", container: "#sophi-widget-modal-container", environment: "production", width: "100%", height: "100%", }); this.widget.on("add_to_cart", async (data: { products: Array<{ productId: string; variantId?: string }> }) => { for (const product of data.products) { await addToCartInYourApp(product.productId, product.variantId); } }); this.widget.on("send_to_checkout", () => { this.close(); this.router.navigateByUrl("/checkout"); }); this.syncCart(); } close(): void { this.isOpen = false; } ngOnChanges(changes: SimpleChanges): void { if (!this.widget) return; if (changes["cartId"] || changes["cartItems"]) { this.syncCart(); } } private syncCart(): void { this.widget.updateUserCart({ cartId: this.cartId, items: this.cartItems, }); } async ngOnDestroy(): Promise { await this.widget?.destroy?.(); this.widget = null; } } ``` ## 5) Required events and cart sync wiring [#5-required-events-and-cart-sync-wiring] ```ts widget.on("add_to_cart", async (data) => { for (const product of data.products) { await addToCartInYourApp(product.productId, product.variantId); } }); widget.on("send_to_checkout", () => { router.navigateByUrl("/checkout"); }); widget.updateUserCart({ cartId: cartId, items: cartItems, }); ``` ## Advanced setup [#advanced-setup] Call advanced methods only after `ready` event or `widget.isReady()`: ```ts widget.on("ready", () => { widget.sendToggleHistory(); widget.triggerProductReply(product); widget.sendProductReply("Can you tell me more about this item?", product); }); ``` * `sendToggleHistory`: open/close history panel * `triggerProductReply`: prefill product reply context * `sendProductReply`: send product-context query ## Consent-aware (pre-consent memory-only mode) [#consent-aware-pre-consent-memory-only-mode] To make the assistant available **before** the visitor accepts cookies, initialize with `storageConsent: false` and upgrade in place when consent is granted. See [Cookie Consent Management](/docs/frontend-integration/cookie-consent) for the full concept. Initialize the widget **once** with the initial consent value, then call `setStorageConsent(true)` whenever your consent service emits a grant — do not destroy and re-create the widget, or the open chat is lost. ```ts import { AfterViewInit, Component, OnDestroy } from "@angular/core"; import { Subscription } from "rxjs"; import { ConsentService } from "./consent.service"; // your own service @Component({ selector: "app-sophi-consent-aware", template: `
`, }) export class SophiConsentAwareComponent implements AfterViewInit, OnDestroy { private widget: any = null; private sub?: Subscription; constructor(private readonly consent: ConsentService) {} async ngAfterViewInit(): Promise { const SophiWidgetClass = (window as any).SophiWebSDK?.SophiWidget; if (!SophiWidgetClass) return; this.widget = new SophiWidgetClass(); await this.widget.init({ apiKey: "YOUR_SOPHI_API_KEY", container: "#sophi-widget-container", environment: "production", width: "100%", height: "100%", storageConsent: this.consent.hasConsent(), // false → memory-only }); // Upgrade the live widget in place when consent is granted. this.sub = this.consent.changes$.subscribe((granted: boolean) => { if (granted) { void this.widget?.setStorageConsent?.(true); } }); } async ngOnDestroy(): Promise { this.sub?.unsubscribe(); await this.widget?.destroy?.(); this.widget = null; } } ``` ## Security [#security] * Keep `YOUR_SOPHI_API_KEY` as placeholder in docs * Do not put real secrets or internal endpoints into client code examples # Framework Integrations URL: /docs/frontend-integration/frameworks Drop-in integration guides for embedding the Sophi assistant widget in React and Angular apps. Framework-specific guides live in this section. # React Integration URL: /docs/frontend-integration/frameworks/react React integration guide with basic (full-page and modal) and advanced widget methods for Sophi. This page is organized in two levels: * **Basic setup**: copy-paste patterns that customers can run with minimal edits * **Advanced setup**: optional runtime controls (`sendToggleHistory`, `sendProductReply`, `triggerProductReply`) For GTM script loading and script-load failures, use [SDK GTM troubleshooting](/docs/frontend-integration/sdk/gtm-integration#troubleshooting). ## Required in every integration [#required-in-every-integration] No matter which UI pattern you use (full page or modal), implement all three: 1. Handle `add_to_cart` 2. Handle `send_to_checkout` 3. Call `widget.updateUserCart(cart)` when cart changes ## Basic setup [#basic-setup] ## 1) Access object from `window` [#1-access-object-from-window] If you load SDK via GTM or script tag: ```ts const SophiWidgetClass = window.SophiWebSDK?.SophiWidget; if (!SophiWidgetClass) { throw new Error("Sophi SDK not loaded yet"); } ``` ## 2) Initialize widget [#2-initialize-widget] ```ts const widget = new SophiWidgetClass(); await widget.init({ apiKey: "YOUR_SOPHI_API_KEY", container: "#sophi-widget-container", environment: "production", }); ``` ## 3) Full-page minimal example [#3-full-page-minimal-example] ```tsx import { useEffect, useRef } from "react"; declare global { interface Window { SophiWebSDK?: { SophiWidget: new () => any }; } } type Cart = { cartId: string; items: Array<{ productId: string; variantId?: string; quantity: number }>; }; export function SophiFullPage({ cart }: { cart: Cart }) { const widgetRef = useRef(null); useEffect(() => { const SophiWidgetClass = window.SophiWebSDK?.SophiWidget; if (!SophiWidgetClass) return; const widget = new SophiWidgetClass(); widgetRef.current = widget; (async () => { await widget.init({ apiKey: "YOUR_SOPHI_API_KEY", container: "#sophi-widget-container", environment: "production", width: "100%", height: "100%", }); widget.on("add_to_cart", async (data: { products: Array<{ productId: string; variantId?: string }> }) => { for (const p of data.products) { await addToCartInYourApp(p.productId, p.variantId); } }); widget.on("send_to_checkout", () => { window.location.href = "/checkout"; }); widget.updateUserCart({ cartId: cart.cartId, items: cart.items, }); })(); return () => { widgetRef.current?.destroy?.(); widgetRef.current = null; }; }, [cart.cartId, cart.items]); useEffect(() => { if (!widgetRef.current) return; widgetRef.current.updateUserCart({ cartId: cart.cartId, items: cart.items, }); }, [cart.cartId, cart.items]); return
; } ``` ## 4) Modal minimal example [#4-modal-minimal-example] ```tsx import { useEffect, useRef, useState } from "react"; export function SophiModal({ cart }: { cart: { cartId: string; items: any[] } }) { const [open, setOpen] = useState(false); const widgetRef = useRef(null); useEffect(() => { if (!open) return; const SophiWidgetClass = window.SophiWebSDK?.SophiWidget; if (!SophiWidgetClass) return; if (widgetRef.current) return; const widget = new SophiWidgetClass(); widgetRef.current = widget; (async () => { await widget.init({ apiKey: "YOUR_SOPHI_API_KEY", container: "#sophi-widget-modal-container", environment: "production", width: "100%", height: "100%", }); widget.on("add_to_cart", async (data: { products: Array<{ productId: string; variantId?: string }> }) => { for (const p of data.products) { await addToCartInYourApp(p.productId, p.variantId); } }); widget.on("send_to_checkout", () => { setOpen(false); window.location.href = "/checkout"; }); widget.updateUserCart({ cartId: cart.cartId, items: cart.items, }); })(); return () => { widgetRef.current?.destroy?.(); widgetRef.current = null; }; }, [open, cart.cartId, cart.items]); useEffect(() => { if (!widgetRef.current) return; widgetRef.current.updateUserCart({ cartId: cart.cartId, items: cart.items, }); }, [cart.cartId, cart.items]); return ( <> {open && (
)} ); } ``` ## 5) Required events and cart sync wiring [#5-required-events-and-cart-sync-wiring] ```ts widget.on("add_to_cart", async (data) => { for (const product of data.products) { await addToCartInYourApp(product.productId, product.variantId); } }); widget.on("send_to_checkout", () => { window.location.href = "/checkout"; }); widget.updateUserCart({ cartId: cartId, items: cartItems, }); ``` ## Advanced setup [#advanced-setup] Call advanced methods only after `ready` event or `widget.isReady()`: ```ts widget.on("ready", () => { widget.sendToggleHistory(); widget.triggerProductReply(product); widget.sendProductReply("Does this match my style?", product); }); ``` * `sendToggleHistory`: open/close history panel * `triggerProductReply`: prefill product reply context * `sendProductReply`: send a typed product-context question ## Consent-aware (pre-consent memory-only mode) [#consent-aware-pre-consent-memory-only-mode] To make the assistant available **before** the visitor accepts cookies, initialize with `storageConsent: false` and upgrade in place when consent is granted. See [Cookie Consent Management](/docs/frontend-integration/cookie-consent) for the full concept. The key points in React: 1. Mount the widget **once** and pass the **initial** consent value via a ref so a later consent change does not destroy / re-init the widget. 2. Watch the consent value and call `widget.setStorageConsent(true)` when it flips to granted. ```tsx import { useEffect, useRef } from "react"; export function SophiConsentAware({ hasConsent }: { hasConsent: boolean }) { const widgetRef = useRef(null); // Read consent inside the init effect without re-running it on change. const hasConsentRef = useRef(hasConsent); useEffect(() => { hasConsentRef.current = hasConsent; }, [hasConsent]); // Mount the widget once — memory-only until consent. useEffect(() => { const SophiWidgetClass = window.SophiWebSDK?.SophiWidget; if (!SophiWidgetClass || widgetRef.current) return; const widget = new SophiWidgetClass(); widgetRef.current = widget; void widget.init({ apiKey: "YOUR_SOPHI_API_KEY", container: "#sophi-widget-container", environment: "production", width: "100%", height: "100%", storageConsent: hasConsentRef.current, // false → memory-only }); return () => { widgetRef.current?.destroy?.(); widgetRef.current = null; }; }, []); // Upgrade the live widget in place when consent is granted. useEffect(() => { if (hasConsent) { void widgetRef.current?.setStorageConsent?.(true); } }, [hasConsent]); return
; } ``` ## Security [#security] * Do not hardcode real keys in source code examples * Keep `YOUR_SOPHI_API_KEY` as placeholder in docs * Keep private values out of GTM/static code # Events URL: /docs/frontend-integration/sdk/events The widget exposes an event-driven API. Attach listeners in your application and keep business logic there. The widget exposes an event-driven API. Attach listeners in your application and keep business logic there. ## Required handlers [#required-handlers] For commerce integrations, these two handlers are required: * `add_to_cart` * `send_to_checkout` Without both handlers, users may see widget suggestions they cannot complete in your storefront flow. ## Register and remove listeners [#register-and-remove-listeners] ```ts const onAddToCart = (data: { products: Array<{ productId: string; variantId?: string }> }) => { console.log("add_to_cart", data.products); }; widget.on("add_to_cart", onAddToCart); // Later, cleanup: widget.off("add_to_cart", onAddToCart); ``` ## Event reference [#event-reference] ### `ready` [#ready] Fired when the widget is ready to use. ```ts widget.on("ready", () => { console.log("Widget ready"); }); ``` ### `add_to_cart` [#add_to_cart] Fired when the widget requests adding products to cart. Host app responsibility: * resolve variant if needed * call your cart API/action * after cart is updated, call `widget.updateUserCart(...)` so widget state stays aligned Payload shape: ```ts type AddToCartEvent = { products: Array<{ productId: string; variantId?: string; }>; }; ``` Example: ```ts widget.on("add_to_cart", async (data) => { for (const product of data.products) { // Resolve variant if needed, then call your cart API/action await addToCartInYourApp(product); } widget.updateUserCart({ cartId: cartId, items: latestCartItems, }); }); ``` ### `add_to_favorite` [#add_to_favorite] Fired when the user toggles favorites on a product and the SDK has validated the payload and resolved an identifier. **Subscription.** Register a listener with `widget.on("add_to_favorite", (data: AddToFavoriteEvent) => { ... })`. The literal `"add_to_favorite"` is also available as `IncomingMessageTypes.ADD_TO_FAVORITE` from the package if you prefer a constant. **Types.** `AddToFavoriteEvent` and `AddToFavoritePayload` are the same shape; import either from `@usesophi/sophi-web-sdk`, along with `Product`, `ProductSummary`, and optionally `IncomingMessageTypes`. **Payload.** The callback receives: * **`product`** (`Product | ProductSummary`): Full product context for your UI or integrations. * **`isFavorite`** (`boolean`): Whether the product is favorited *after* the user’s action (the resulting state). * **`productIdentifier`** (`string`): Convenience id string computed by the SDK from `product` so you do not need your own fallback logic. **`productIdentifier` resolution.** The SDK derives the value in this order: use a non-empty `externalId` on `product` if present; else `String(product.id)` if `id` is present; else a non-empty `productId` on `product` if present. If none of these apply, the SDK does not emit `add_to_favorite`. **Invalid or missing data.** If validation fails or `productIdentifier` cannot be derived, the SDK does not fire `add_to_favorite` (it may log a console warning). You do not need to handle malformed payloads for this event. Example: ```ts import { type AddToFavoriteEvent, IncomingMessageTypes } from "@usesophi/sophi-web-sdk"; widget.on("add_to_favorite", (data: AddToFavoriteEvent) => { syncWishlist(data.productIdentifier, data.isFavorite, data.product); }); // Equivalent: widget.on(IncomingMessageTypes.ADD_TO_FAVORITE, (data: AddToFavoriteEvent) => { syncWishlist(data.productIdentifier, data.isFavorite, data.product); }); ``` ### `send_to_checkout` [#send_to_checkout] Fired when user should be redirected to checkout. Host app responsibility: * navigate user to your checkout route immediately * optionally close/hide any custom modal around the widget ```ts widget.on("send_to_checkout", () => { window.location.href = "/checkout"; }); ``` ### `error` [#error] Fired when the widget reports an error. ```ts widget.on("error", (error: Error) => { console.error("Sophi error", error.message); }); ``` # GTM Integration URL: /docs/frontend-integration/sdk/gtm-integration Load the Sophi Web SDK via Google Tag Manager with consent-aware script injection and app-side initialization. For **`@usesophi/sophi-web-object`** (commerce tracking without the widget), use [Web Object GTM integration](/docs/frontend-integration/web-object/gtm-integration). Use this setup when the **Sophi Web SDK** (chat widget) is loaded by Google Tag Manager (GTM), not from npm runtime imports. For most teams, the recommended GTM setup is **load on all pages + consent-aware storage upgrade**. See [Cookie Consent Management](/docs/frontend-integration/cookie-consent). ## Browser global [#browser-global] When loaded by script, the SDK is exposed as: ```ts window.SophiWebSDK.SophiWidget; ``` ## Script URL (current) [#script-url-current] Use this script URL in GTM-injected script element: ```text https://cdn.jsdelivr.net/npm/@usesophi/sophi-web-sdk@latest/dist/index.umd.js ``` ## Cookie consent management (recommended) [#cookie-consent-management-recommended] This is the default model we recommend: load Sophi on **every page**, keep it memory-only before consent, then upgrade to persistent storage when the visitor accepts cookies. ### 1. Load the SDK on all pages [#1-load-the-sdk-on-all-pages] Use a GTM tag that injects the SDK on page load regardless of consent: ```html ``` ### 2. Initialize memory-only [#2-initialize-memory-only] Initialize with `storageConsent: false` so no cookies are written yet: ```ts const SophiWidgetClass = await waitForSophiSDK(5000); const widget = new SophiWidgetClass(); await widget.init({ apiKey: "YOUR_SOPHI_API_KEY", container: "#sophi-widget-container", userId, userName, environment: "production", storageConsent: false, // memory-only until consent }); ``` ### 3. Upgrade on the consent-granted event [#3-upgrade-on-the-consent-granted-event] Add a small Custom HTML tag (or app-side listener) tied to your consent-updated event that upgrades the live widget in place: ```html ``` If you manage the `SophiWidget` instance yourself, call `widget.setStorageConsent(true)` from your app code instead. ## Strict consent-gated GTM trigger configuration [#strict-consent-gated-gtm-trigger-configuration] ### Tag [#tag] * **Tag Type:** `Custom HTML` * **Purpose:** Load SDK only after functional consent is granted (strict policy) ### Triggers [#triggers] Attach the same Custom HTML tag to both triggers: 1. **Consent on Granted** 2. **Page onload** This covers: * consent already granted before page load * consent granted during current session ## Custom HTML behavior [#custom-html-behavior] The GTM tag should: 1. Read and parse `cookie_consent` cookie as JSON 2. Check `parsed.functional === true` 3. If true, inject script URL (above) once 4. On script load, dispatch: * `sophi:sdk_loaded` * `sophi:init` ```html ``` ## App-side wait flow [#app-side-wait-flow] Initialize the widget only when: * functional consent exists * `waitForSophiSDK(5000)` resolves * readiness is confirmed by either: * `sophi:init` window event * `window.SophiWebSDK.SophiWidget` presence ```ts const SophiWidgetClass = await waitForSophiSDK(5000); const widget = new SophiWidgetClass(); await widget.init({ apiKey: "YOUR_SOPHI_API_KEY", container: "#sophi-widget-container", userId, userName, environment: "production", }); ``` Keep configuration and event listeners in application code.\ GTM should only handle consent-gated script loading. ## Troubleshooting [#troubleshooting] ### Script does not load from GTM [#script-does-not-load-from-gtm] * Confirm the Custom HTML tag fires on the expected consent triggers. * Confirm your consent condition resolves to granted state. * Open GTM Preview mode and verify the tag actually fires on page load and consent change. ### `window.SophiWebSDK` is undefined [#windowsophiwebsdk-is-undefined] * SDK script has not loaded yet, or load was blocked by consent/trigger logic. * Confirm jsDelivr URL is valid and reachable. * Delay widget init until your wait strategy succeeds (`sophi:init` event or `waitForSophiSDK`). ### `waitForSophiSDK` timeout [#waitforsophisdk-timeout] * Increase timeout if consent workflow is delayed. * Ensure consent update event is pushed when user accepts. * Ensure GTM trigger for consent-updated path exists (not only initial page load). ### Init runs before container exists [#init-runs-before-container-exists] * Make sure `#sophi-widget-container` exists in DOM before calling `widget.init`. * In React/Angular, initialize after component mount/view init. ### Consent granted after page load, but widget still missing [#consent-granted-after-page-load-but-widget-still-missing] * Attach the same SDK load tag to both: 1. initial granted-consent trigger 2. mid-session consent-granted trigger * Confirm the mid-session trigger is tied to your consent update event. ### Duplicate init / repeated listeners [#duplicate-init--repeated-listeners] * Keep a single widget instance reference and guard against re-init. * On teardown, call `widget.destroy()` and clear references. * Register listeners once per widget instance to avoid duplicate callbacks. ## Optional helper example (`waitForSophiSDK`) [#optional-helper-example-waitforsophisdk] This is a **reference example only** to help teams implementing GTM-based loading. You do **not** have to copy this exact helper; any equivalent wait strategy is fine. ```ts const SOPHI_INIT_EVENT = "sophi:init"; const POLL_INTERVAL_MS = 150; export function waitForSophiSDK(timeoutMs: number): Promise any> { return new Promise((resolve, reject) => { if (typeof window === "undefined") { reject(new Error("waitForSophiSDK: window is undefined")); return; } const w = window as Window & { SophiWebSDK?: { SophiWidget: new () => any } }; let timeoutId: ReturnType | null = null; let pollId: ReturnType | null = null; function cleanup() { window.removeEventListener(SOPHI_INIT_EVENT, onInit); if (timeoutId) clearTimeout(timeoutId); if (pollId) clearInterval(pollId); timeoutId = null; pollId = null; } function tryResolve() { if (w.SophiWebSDK?.SophiWidget) { cleanup(); resolve(w.SophiWebSDK.SophiWidget); } } function onInit() { tryResolve(); } // Already available tryResolve(); if (w.SophiWebSDK?.SophiWidget) return; // GTM might dispatch `sophi:init` window.addEventListener(SOPHI_INIT_EVENT, onInit); // Or set `window.SophiWebSDK` without the event pollId = setInterval(tryResolve, POLL_INTERVAL_MS); timeoutId = setTimeout(() => { cleanup(); reject(new Error(`Sophi SDK did not load within ${timeoutMs}ms`)); }, timeoutMs); }); } ``` # SDK Overview URL: /docs/frontend-integration/sdk Use @usesophi/sophi-web-sdk to embed the Sophi assistant in your storefront and handle widget events in your application code. Use `@usesophi/sophi-web-sdk` to embed the Sophi assistant in your storefront and handle widget events in your application code. For **storefront event tracking** without the widget (page, product, basket, purchase, etc.), use the separate package **`@usesophi/sophi-web-object`** — see the [Web Object documentation](/docs/frontend-integration/web-object). Keep script loading and consent management separate from widget configuration. If you use GTM, GTM should only load the SDK script after consent; initialization should stay in your app code. ## Required for production [#required-for-production] Every customer integration should include these three items: 1. Handle `add_to_cart` event. 2. Handle `send_to_checkout` event. 3. Call `widget.updateUserCart(cart)` whenever host cart data changes. Without these, cart synchronization and checkout handoff will be incomplete. ## Security reminder [#security-reminder] * Keep real keys and tokens out of docs, GTM tags, and static frontend bundles. * Use placeholders such as `YOUR_SOPHI_API_KEY`. * Keep sensitive values server-side when possible. ## What this SDK provides [#what-this-sdk-provides] * Framework-agnostic widget initialization * Browser script support via `window.SophiWebSDK` * Typed events for commerce actions * Runtime methods for user/cart updates and widget lifecycle ## Public API surface [#public-api-surface] * Class: `SophiWidget` * Init: `await widget.init(config)` * Events: `ready`, `add_to_cart`, `add_to_favorite`, `send_to_checkout`, `error` * Methods: `on`, `off`, `updateUserCart`, `sendToggleHistory`, `sendTextMessage`, `sendProductReply`, `openProductById`, `triggerProductReply`, `show`, `hide`, `isReady`, `destroy` ## Step-by-step integration path [#step-by-step-integration-path] 1. Access widget object from `window` (if script/GTM mode) or npm import. 2. Initialize widget. 3. Wire required events (`add_to_cart`, `send_to_checkout`). 4. Sync cart via `updateUserCart`. 5. Add advanced methods (`sendToggleHistory`, `sendProductReply`, `triggerProductReply`) only if needed. Recommended reading order: * [GTM Integration](/docs/frontend-integration/sdk/gtm-integration) for loading and troubleshooting script/runtime errors * [Events](/docs/frontend-integration/sdk/events) for required event handlers * [Methods](/docs/frontend-integration/sdk/methods) for required cart sync and advanced controls * [Framework guides](/docs/frontend-integration/frameworks) for copy-paste React/Angular examples (basic and advanced) ## Documentation map [#documentation-map] # Initialization URL: /docs/frontend-integration/sdk/initialization Initialize the widget in your application after the SDK is loaded and the target container is available in the DOM. Initialize the widget in your application after the SDK is loaded and the target container is available in the DOM. ## Configuration object [#configuration-object] The `init()` method accepts the following configuration: * Required * `apiKey: string` * `container: string | HTMLElement` * Optional * `userId?: string` * `userName?: string` * `metadata?: Metadata` * `width?: string` * `height?: string` * `onReady?: () => void` * `onError?: (error: Error) => void` * `environment?: "test" | "production"` * `storageConsent?: boolean` ### `storageConsent` [#storageconsent] | Type | Default | Description | | --------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `boolean` | `true` | When `true`, the widget persists the session in cookies (current behavior). Set to `false` for **pre-consent memory-only mode**: no cookies are written or read, the session lives only in memory, and the visitor starts fresh on every load. Upgrade to persistent mode at runtime with [`widget.setStorageConsent(true)`](/docs/frontend-integration/sdk/methods#await-widgetsetstorageconsentgranted). See [Cookie Consent Management](/docs/frontend-integration/cookie-consent). | ## Metadata [#metadata] Use `metadata` to provide structured ecommerce context to Sophi. ### Minimum requirements [#minimum-requirements] * `metadata.profile.userId: string` * `metadata.profile.isLoggedIn: boolean` * `metadata.visitedPages: Array<{ url: string; timestamp: string }>` Each `timestamp` value must use ISO-8601 format (for example, `"2026-04-01T10:46:10Z"`). ### Recommended shape [#recommended-shape] ```ts type Metadata = { profile: { userId: string; isLoggedIn: boolean; email?: string; firstName?: string; lastName?: string; locale?: string; country?: string; currency?: string; attributes?: Record; }; visitedPages: Array<{ url: string; timestamp: string; type?: string; productId?: string; title?: string; attributes?: Record; }>; }; ``` `visitedPages[*].type` is intentionally free-form so integrators can map their own page taxonomy. ## Example [#example] ```ts const widget = new window.SophiWebSDK.SophiWidget(); await widget.init({ apiKey: "YOUR_SOPHI_API_KEY", container: "#sophi-widget-container", userId: "user-123", userName: "Jane Doe", environment: "production", width: "100%", height: "600px", metadata: { profile: { userId: "user-123", isLoggedIn: true, email: "jane@example.com", firstName: "Jane", lastName: "Doe", locale: "en-US", country: "US", currency: "USD", }, visitedPages: [ { url: "/women/shoes", timestamp: "2026-04-01T10:45:00Z", type: "category", attributes: { categoryId: "cat-women-shoes", }, }, { url: "/product/nike-air-max-001", timestamp: "2026-04-01T10:46:10Z", type: "pdp", attributes: { productId: "sku-001", }, }, ], }, onReady: () => { console.log("Sophi widget ready"); }, onError: (error) => { console.error("Sophi widget init error", error); }, }); ``` ## Recommended flow [#recommended-flow] 1. Confirm cookie consent has been obtained, if required by your policy. 2. Confirm the SDK script is loaded (or the npm bundle is ready). 3. Confirm the widget container is mounted in the DOM. 4. Call `await widget.init(...)`. 5. Register event listeners with `on(...)`. If you want the assistant to be available before consent, initialize with `storageConsent: false` and upgrade on consent at runtime. Store `apiKey` in your application configuration layer. Do not hardcode production secrets in publicly shared examples. To make the assistant available before the visitor accepts cookies, initialize with `storageConsent: false` and call [`widget.setStorageConsent(true)`](/docs/frontend-integration/sdk/methods#await-widgetsetstorageconsentgranted) when consent is granted. See [Cookie Consent Management](/docs/frontend-integration/cookie-consent). # Installation URL: /docs/frontend-integration/sdk/installation Install @usesophi/sophi-web-sdk from npm and initialize the SophiWidget in your storefront. Install the SDK from npm: ```bash npm install @usesophi/sophi-web-sdk ``` Package reference: * [@usesophi/sophi-web-sdk on npm](https://www.npmjs.com/package/@usesophi/sophi-web-sdk) ## Import and create widget [#import-and-create-widget] ```ts import { SophiWidget } from "@usesophi/sophi-web-sdk"; const widget = new SophiWidget(); ``` ## Initialize [#initialize] ```ts await widget.init({ apiKey: "YOUR_SOPHI_API_KEY", container: "#sophi-widget-container", userId, userName, environment: "production", metadata: { profile: { userId: "user-123", isLoggedIn: true, email: "jane@example.com", firstName: "Jane", lastName: "Doe", locale: "en-US", country: "US", currency: "USD", }, visitedPages: [ { url: "/women/shoes", timestamp: "2026-04-01T10:45:00Z", type: "category", }, { url: "/product/nike-air-max-001", timestamp: "2026-04-01T10:46:10Z", type: "pdp", attributes: { productId: "sku-001", }, }, ], }, }); ``` Minimum metadata requirements: * `metadata.profile.userId` * `metadata.profile.isLoggedIn` * `metadata.visitedPages[]` with `url` and ISO-8601 `timestamp` on each item `visitedPages[*].type` is optional and intentionally free-form. If your project loads the SDK through Google Tag Manager (GTM) instead of npm, see [GTM Integration](/docs/frontend-integration/sdk/gtm-integration). # Methods URL: /docs/frontend-integration/sdk/methods Reference for the SophiWidget instance methods — lifecycle (init, destroy, isReady), visibility, cart updates, sending messages and product replies, and event subscription. ## Lifecycle and UI [#lifecycle-and-ui] ### `await widget.init(config)` [#await-widgetinitconfig] Initializes the widget and mounts an iframe into the given container. ### `await widget.destroy()` [#await-widgetdestroy] Destroys the widget, removes listeners and iframe, and resets internal state. ```ts await widget.destroy(); ``` ### `widget.isReady()` [#widgetisready] Returns whether the widget is initialized and ready. ```ts if (widget.isReady()) { console.log("Widget active"); } ``` ### `widget.show()` / `widget.hide()` [#widgetshow--widgethide] Toggles iframe visibility. ```ts widget.hide(); widget.show(); ``` ### `await widget.setStorageConsent(granted)` [#await-widgetsetstorageconsentgranted] Updates cookie/storage consent at runtime **without losing the open chat**. Use it together with `storageConsent: false` at [init](/docs/frontend-integration/sdk/initialization#storageconsent) to run the widget in [Cookie Consent Management](/docs/frontend-integration/cookie-consent). | Parameter | Type | Required | Description | | --------- | --------- | -------- | -------------------------------------------------- | | `granted` | `boolean` | Yes | `true` to enable persistence, `false` to withdraw. | When granting consent: * The current in-memory session is persisted to cookies. * For a logged-in user (a `userId` was provided at init), the anonymous guest session is merged into that user account. * The chat iframe promotes its in-memory store to `localStorage`, so the conversation survives the next reload — no re-init required. ```ts // Visitor accepted cookies in your consent banner await widget.setStorageConsent(true); // Visitor withdrew consent await widget.setStorageConsent(false); ``` ## Data and communication [#data-and-communication] ### `widget.updateUserCart(cart)` [#widgetupdateusercartcart] Pushes latest cart state to widget runtime. Call this method after every meaningful cart change (initial load, add/remove, quantity change, cart restore). Otherwise widget recommendations may be based on stale cart state. Minimal sequence: ```ts await widget.init({ apiKey: "YOUR_SOPHI_API_KEY", container: "#sophi-widget-container" }); widget.on("add_to_cart", async (data) => { await addToCartInYourApp(data.products); widget.updateUserCart({ cartId: cartId, items: cartItems }); }); widget.on("send_to_checkout", () => { window.location.href = "/checkout"; }); ``` ```ts widget.updateUserCart({ cartId: "cart-001", items: [ { productId: "p1", variantId: "v1", quantity: 1 }, { productId: "p2", variantId: "v2", quantity: 2 }, ], }); ``` ### `widget.sendToggleHistory()` [#widgetsendtogglehistory] Toggles the chat history panel inside the widget. No-op with a `console.warn` if the widget is not initialized. ```ts widget.sendToggleHistory(); ``` ## Chat messages [#chat-messages] These methods let the host application send typed chat messages directly into the Sophi widget. All three are no-ops — they log a `console.warn` and return immediately — when the widget iframe has not yet initialised. ### `widget.sendTextMessage(query)` [#widgetsendtextmessagequery] Sends a plain chat message to the widget with no product or outfit context. | Parameter | Type | Required | Description | | --------- | -------- | -------- | ---------------------- | | `query` | `string` | Yes | The user's text query. | Posts the following message to the iframe: ```ts { type: "send_text_message", data: { query } } ``` **Example** ```ts widget.sendTextMessage("What should I wear today?"); ``` *** ### `widget.sendProductReply(product, opts?)` [#widgetsendproductreplyproduct-opts] Sends a chat message scoped to a specific product card. `productId` is derived automatically from `product.id` — do not pass it separately. Supply `opts.query` and `opts.isOutfit` when the query intents to outfit context. | Parameter | Type | Required | Description | | --------- | --------------------- | -------- | ------------------------------------- | | `product` | `string` or `number` | Yes | The product the user is asking about. | | `opts` | `ProductReplyOptions` | No | Optional outfit or query context. | Posts the following message to the iframe: ```ts { type: "send_product_reply", data: { query, externalProductId isOutfit?, // from opts.isOutfit, if provided } } ``` **Type definitions** ```ts interface ProductReplyOptions { query?: string; // Optional follow-up question or message to send alongside the product reply isOutfit?: boolean; // Set to true when the query involves outfit context } ``` **Examples** ```ts // Basic external Product Id query widget.sendProductReply(externalProductId, { query: "Does this come in blue?" }); // Product that belongs to an outfit recommendation widget.sendProductReply(externalProductId, { query: "Tell me about this jacket" isOutfit: true, }); // Open chat with given external product widget.sendProductReply(externalProductId, { isOutfit: true, }); ``` *** All three methods (`sendTextMessage`, `sendProductReply`) silently no-op and emit a `console.warn` when called before the widget iframe is ready. Call them only after the `ready` event has fired or `widget.isReady()` returns `true`. ## UI controls [#ui-controls] `openProductById` and `triggerProductReply` drive the widget's **visual state** directly via `postMessage`. They do **not** send a query to the chat assistant, trigger a chat response, or alter conversation history. Use the [Chat messages](#chat-messages) methods when you want the chat assistant to respond. These methods are no-ops — they log a `console.warn` and return immediately — when the widget iframe has not yet initialised. ### `widget.openProductById(externalProductId, reason?)` [#widgetopenproductbyidexternalproductid-reason] Instructs the widget to fetch the product and open the product detail panel, identical to the user clicking a product card in the chat results. The iframe resolves the full product data itself from the given ID. | Parameter | Type | Required | Description | | ------------------- | -------------------- | -------- | ------------------------------------------------------- | | `externalProductId` | `number` or `string` | Yes | Product ID to open. | | `reason` | `string` | No | Recommendation reason text shown alongside the product. | Posts the following message to the iframe: ```ts { type: "open_product_by_id", data: { externalProductId, reason? } } ``` **Type definition** ```ts interface OpenProductByIdPayload { externalProductId: number; reason?: string; } ``` **Examples** ```ts // Open the product detail panel for product ID 123 widget.openProductById(123); // Open with a recommendation reason widget.openProductById(123, "Matches your style preferences"); ``` *** ### `widget.triggerProductReply(product)` ⚠️ Deprecated [#widgettriggerproductreplyproduct-️-deprecated] This method is deprecated and may be removed in a future release. Use [`widget.sendProductReply()`](#widgetsendproductreplyproduct-opts) instead. Sets a product as the active reply context in the chat input bar, identical to the user clicking **Ask a Question** on a product card. The reply popover appears above the input with the product pre-filled. The user then types their question and sends it — the outgoing message will be delivered as a product-context reply. | Parameter | Type | Required | Description | | --------- | ---------------- | -------- | -------------------------------------------------------- | | `product` | `ProductSummary` | Yes | Full product summary object to set as the reply context. | Posts the following message to the iframe: ```ts { type: "trigger_product_reply", data: { product } } ``` **Type definition** ```ts interface TriggerProductReplyPayload { product: ProductSummary; } ``` See [`sendProductReply`](#widgetsendproductreplyproduct-opts) for the full `ProductSummary` interface definition. **Example** ```ts // Pre-fill the chat input reply bar with a product context widget.triggerProductReply(product); ``` *** Both `openProductById` and `triggerProductReply` silently no-op and emit a `console.warn` when called before the widget iframe is ready. Call them only after the `ready` event has fired or `widget.isReady()` returns `true`. ## Advanced methods quick list [#advanced-methods-quick-list] These methods are typically used in advanced UI flows: * `sendToggleHistory` * `sendProductReply` * `triggerProductReply` ## Events API [#events-api] ### `widget.on(event, handler)` [#widgetonevent-handler] Registers an event listener. ### `widget.off(event, handler)` [#widgetoffevent-handler] Removes a previously registered listener. # Security Best Practices URL: /docs/frontend-integration/sdk/security-best-practices Use these practices when integrating the widget in production environments. Use these practices when integrating the widget in production environments. ## Protect secrets and credentials [#protect-secrets-and-credentials] * Never commit real API keys to source control * Use environment variables or secure config management * Use placeholders in documentation and examples (`YOUR_SOPHI_API_KEY`) Do not copy production keys into GTM tags, public snippets, or screenshots. ## Keep responsibility boundaries clear [#keep-responsibility-boundaries-clear] * GTM (or tag manager): script loading after consent * App code: widget initialization, config values, event handlers, cart/checkout actions This separation reduces accidental credential exposure and keeps business logic testable. ## Avoid documenting internal implementation details [#avoid-documenting-internal-implementation-details] Public docs should avoid: * Session/token payload internals * Internal service URLs and non-production hosts * Private message contracts not intended as public API * Internal cookie/session storage mechanics ## Handle consent and lifecycle correctly [#handle-consent-and-lifecycle-correctly] * Load SDK only after required consent is granted * Initialize widget only when container is mounted * Destroy widget on teardown to remove listeners and iframe ## Safe error handling [#safe-error-handling] * Attach `onError` and `error` listeners * Log generic errors for observability * Avoid printing sensitive config values in logs # Configuration URL: /docs/frontend-integration/web-object/configuration All runtime settings for the Sophi Web Object, provided via window.sophi_object.config including apiKey, userId, maxRetries, debounceMs, debug, and storageConsent. All runtime settings are provided under **`window.sophi_object.config`**. The SDK reads this object when it starts. ## `SophiConfig` [#sophiconfig] | Field | Required | Default | Description | | ---------------- | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `apiKey` | **Yes** | — | API key issued for your Sophi tenant; required to open a browser session | | `userId` | No | — | External user id from your auth system; used in session creation and guest→logged-in merge | | `maxRetries` | No | `3` | Retries per event after failed send | | `debounceMs` | No | `300` | Milliseconds to wait before sending batched events after an update | | `debug` | No | `false` | When `true`, internal `log()` output goes to the console | | `storageConsent` | No | `true` | When `false`, the Web Object loads **passively**: no session, no cookies, and **no events are sent** until consent is granted. See [Cookie Consent Management](/docs/frontend-integration/cookie-consent). | If `apiKey` is missing, startup stops with a warning and no tracking runs. ## Example [#example] ```javascript window.sophi_object = { config: { apiKey: 'YOUR_API_KEY', userId: 'usr-123', maxRetries: 3, debounceMs: 300, debug: false } }; ``` ## Pre-consent passive mode (no consent) [#pre-consent-passive-mode-no-consent] When `storageConsent` is `false`, the Web Object stays **completely passive**: * No session is created and no cookies are written. * `SophiTracker.push(...)` calls are dropped — **no events reach the backend**. * Only `window.SophiTracker` is exposed so you can activate it later. Call [`SophiTracker.setConsent(true)`](/docs/frontend-integration/web-object/sophi-tracker#sophitrackersetconsentgranted) once the visitor grants consent to start a session, write cookies, and begin sending events (including the current `sophi_object` snapshot). See [Cookie Consent Management](/docs/frontend-integration/cookie-consent). ## Session and cookies [#session-and-cookies] After your configuration is valid, the SDK establishes or restores an **authenticated browser session** with Sophi over HTTPS and receives credentials it can use when sending events. Those credentials are stored in first-party cookies (30-day lifetime, `path=/`, **`SameSite=Lax`**). Cookie names are namespaced by API key: | Suffix (after `sophi-{apiKey}-`) | Content | | -------------------------------- | ---------------------------------------------------------------------------------------------------------- | | `access-token` | Short-lived credential used when delivering events | | `client-id` | Stable visitor identifier managed by Sophi | | `external-user-id` | Copy of `config.userId` from when the session was created (used when linking guest and logged-in activity) | When a shopper was anonymous and later logs in, the SDK may ask Sophi to **associate the existing anonymous profile** with the user id you provide in `config.userId`, so continuity is preserved without you managing that protocol yourself. To reset session state (e.g. logout), use [`SophiTracker.updateUser`](/docs/frontend-integration/web-object/sophi-tracker) or the underlying session clear behaviour described there. ## Debug logging [#debug-logging] With `debug: true`, the SDK prints diagnostic messages via its internal logger (implementation-defined prefix). Disable in production to avoid noise and accidental disclosure of state. ## Related [#related] * [Data and auth flows](/docs/frontend-integration/web-object/flows) * [SophiTracker API](/docs/frontend-integration/web-object/sophi-tracker) — `updateUser` after login # Event types URL: /docs/frontend-integration/web-object/event-types Events sent to the backend use ExternalEventType on the envelope. Values are lowercase snake-case strings. Events sent to the backend use **`ExternalEventType`** on the envelope. Values are lowercase snake-case strings. ## Allowed values [#allowed-values] | `event_type` | Typical meaning | | ------------------ | ---------------------------------------------------------- | | `page_view` | Page context / navigation | | `identify` | User context and consent | | `product_view` | Product detail view | | `listing_view` | Category, search, or brand listing | | `add_to_cart` | Item added; usually needs `product` + `basket` | | `remove_from_cart` | Item removed; usually needs `product` + `basket` | | `basket_view` | Cart page or basket snapshot | | `checkout_started` | Checkout entry (often with `basket`) | | `purchase` | Completed order (`transaction`) | | `custom` | Tenant-defined (fallback when no specific mapping applies) | ## Default mapping (assignment trigger) [#default-mapping-assignment-trigger] When you assign to a watched key and do **not** override `event_type`, defaults are: | Trigger key | Default `event_type` | | ------------- | -------------------- | | `page` | `page_view` | | `user` | `identify` | | `product` | `product_view` | | `basket` | `basket_view` | | `listing` | `listing_view` | | `transaction` | `purchase` | ## Overriding `event_type` [#overriding-event_type] 1. Set **`sophi_object.event_type`** before the assignment that should use the override, or 2. Pass **`event_type`** inside [`SophiTracker.push()`](/docs/frontend-integration/web-object/sophi-tracker) — it applies to **all** collection triggers fired by that call. `add_to_cart`, `remove_from_cart`, and `checkout_started` typically require an explicit override plus the right objects in `sophi_object` (see below). ## Semantic “required” context [#semantic-required-context] Downstream consumers expect certain objects per event. The SDK **does not automatically add** missing optional sections; it sends the payload derived from what you set on `sophi_object`. Design your integration so each event type includes the right context. | `event_type` | Expected context (semantic) | Default trigger key | | ------------------ | --------------------------- | ------------------------- | | `page_view` | `page` | `page` | | `identify` | `page`, `user` | `user` | | `product_view` | `page`, `product` | `product` | | `listing_view` | `page`, `listing` | `listing` | | `add_to_cart` | `page`, `product`, `basket` | use `push` + `event_type` | | `remove_from_cart` | `page`, `product`, `basket` | use `push` + `event_type` | | `basket_view` | `page`, `basket` | `basket` | | `checkout_started` | `page`, `basket` | use `push` + `event_type` | | `purchase` | `page`, `transaction` | `transaction` | | `custom` | `page` (minimum) | fallback | ## Related [#related] * [Types reference](/docs/frontend-integration/web-object/types) — `EventEnvelope` * [Integration patterns](/docs/frontend-integration/web-object/integration-patterns) — add to cart, purchase * [Validation](/docs/frontend-integration/web-object/validation) # Data and auth flows URL: /docs/frontend-integration/web-object/flows Overview of the event pipeline, auth flow, and session lifecycle for the Sophi Web Object SDK. ## Event pipeline [#event-pipeline] 1. Your code sets a **tracked** property on `window.sophi_object` or calls **`SophiTracker.push`**. 2. The SDK applies the update, then prepares one or more **events** from the current data layer state. 3. Each event includes a **`page`** context, the resolved **`event_type`**, and any relevant objects (`user`, `product`, etc.). Invalid or incomplete data is skipped with a console warning. 4. Events wait briefly (**`debounceMs`**) so rapid updates can batch; failed deliveries are **retried** with exponential backoff up to **`maxRetries`**. 5. Delivery uses the **active session** established at startup: payloads are sent to Sophi over **HTTPS** as JSON. When the visitor hides or leaves the tab, the SDK attempts a **best-effort flush** (for example via the browser’s unload / beacon mechanisms) so late events are less likely to be lost. ## Auth and session [#auth-and-session] * Session creation is **deduplicated** while a request is already in flight. * On **logout or user switch**, session state and cookies are cleared so the next activity uses a fresh session. * When an anonymous visitor **logs in**, the SDK coordinates with Sophi so the existing anonymous activity can be **linked** to your `config.userId` where applicable. After login in your app, call **`SophiTracker.updateUser(externalId)`** then push **`user`** / other context as needed. See [SophiTracker API](/docs/frontend-integration/web-object/sophi-tracker). ## Related [#related] * [Configuration](/docs/frontend-integration/web-object/configuration) * [Event types](/docs/frontend-integration/web-object/event-types) # GTM integration URL: /docs/frontend-integration/web-object/gtm-integration How to load @usesophi/sophi-web-object (the storefront event-tracking SDK) through Google Tag Manager, including recommended split and all-in-GTM configurations. Use this page when you load **`@usesophi/sophi-web-object`** (the browser data-layer / analytics SDK) through **Google Tag Manager**. It is **not** the chat widget: for **`@usesophi/sophi-web-sdk`** (`SophiWidget`), see [SDK GTM integration](/docs/frontend-integration/sdk/gtm-integration). ## Prerequisites [#prerequisites] * Google Tag Manager is installed on your site. * Tags that load Sophi fire only when your **privacy / consent** rules allow (for example Google Consent Mode **analytics\_storage** granted, or an equivalent you use). * `window.sophi_object.config.apiKey` is available **before** `sophi.min.js` runs, unless you use the [all-in-GTM variant](#all-in-gtm-variant-simple-sites) below. ## Recommended split (apps and SPAs) [#recommended-split-apps-and-spas] For most modern storefronts, keep **data and API keys in your application** and use **GTM only to inject the script**: | Responsibility | Where | | ------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | | `window.sophi_object` — `config` (and optionally `userId`, `debug`, …), `page`, `product`, `basket`, `listing`, `transaction`, `user` | Your site / SPA, updated when consent allows | | Loading `sophi.min.js` from jsDelivr | GTM (Custom HTML tag, consent-gated) | This avoids duplicating commerce payloads in GTM, supports **multi-brand API keys** in code, and matches the pattern described in our [integration patterns](/docs/frontend-integration/web-object/integration-patterns). ### GTM variable (easy upgrades) [#gtm-variable-easy-upgrades] Create a **Constant** variable in GTM, for example: | Variable name | Type | Value | | ----------------------------- | -------- | --------------------------------------------------------------------------------- | | `Sophi Web Object script URL` | Constant | `https://cdn.jsdelivr.net/npm/@usesophi/sophi-web-object@x.y.z/dist/sophi.min.js` | Replace `x.y.z` with a **pinned** semver for production. When you upgrade the SDK, **change this one variable** and **publish** the container—no need to edit multiple tags. `@latest` is convenient for development. In production, pin a version so a new npm release cannot change behaviour until you deliberately update the Constant. ### Two triggers, one tag [#two-triggers-one-tag] Visitors may already have granted analytics consent when the page loads, or they may accept cookies **during** the session. Attach the **same** “load Sophi script” tag to **both** cases: 1. **Consent on first paint** — For example a **Consent Initialization** trigger (or your CMP’s equivalent) that fires when **analytics** consent is already granted for this page view. 2. **Consent granted mid-session** — For example a **Custom Event** trigger when your banner pushes a consent update to `dataLayer`, with `analytics === true` (names depend on your implementation). Both triggers should run the identical Custom HTML (below) so `sophi.min.js` loads exactly once when consent becomes true, whether that was before or after load. #### Example: Consent Mode + dataLayer (illustrative) [#example-consent-mode--datalayer-illustrative] If you mirror **Google Consent Mode** and push `cookie_consent_updated` when the user saves preferences: * **Variable:** Data Layer Variable reading `cookie_consent.analytics` (or your field name). * **Trigger A:** Consent Initialization — All Pages, with condition **analytics\_storage** granted (or your CMP’s mapping). * **Trigger B:** Custom Event `cookie_consent_updated`, with condition **DLV equals `true`**. Adapt names and conditions to your CMP; the important part is covering **both** initial and delayed consent. ### Custom HTML tag (script only) [#custom-html-tag-script-only] When your application already sets `window.sophi_object.config` (and other keys) before or when the tag fires: ```html ``` Use the exact variable name you defined in GTM (the `{{...}}` placeholder must match). If the script might fire more than once during navigation tests, you can guard with a small check (optional): ```html ``` The SDK itself waits for `DOMContentLoaded` before bootstrapping; `async` on the script tag is supported. ## All-in-GTM variant (simple sites) [#all-in-gtm-variant-simple-sites] For **static** sites you can set **minimal config in the same tag** that loads the bundle. Prefer **Option A** (separate script `src`); **Option B** inlines the bundle and increases container size. ### Option A — load from jsDelivr [#option-a--load-from-jsdelivr] 1. Create a **Custom HTML** tag. 2. Ensure `window.sophi_object` exists with at least `config.apiKey`, then load the script: ```html ``` 3. Fire the tag only when analytics consent is granted (same consent principles as above). Pin `x.y.z` in production. ### Option B — inline `sophi.min.js` [#option-b--inline-sophiminjs] 1. Paste the full contents of `sophi.min.js` from the published package **after** the small `window.sophi_object` config block. 2. Use the same triggers as Option A. Inlining removes one CDN round-trip at the cost of a **large** GTM payload. If `window.sophi_object` is defined in an **earlier** tag, define it there and keep this tag’s HTML to **only** the script `src` line (recommended pattern in [Recommended split](#recommended-split-apps-and-spas)). *** For consent-sensitive fields (`UserObject`, PII), see [Configuration](/docs/frontend-integration/web-object/configuration) and [Types reference](/docs/frontend-integration/web-object/types). ## QA checklist [#qa-checklist] 1. With consent **denied**, confirm **no** Sophi tracking requests (or script load, depending on your triggers). 2. After consent **granted**, confirm `sophi.min.js` loads from your pinned URL. 3. Confirm `window.sophi_object.config` has a valid `apiKey` before or when the script runs (unless you rely on the SDK’s post-start updates—see [SophiTracker](/docs/frontend-integration/web-object/sophi-tracker)). 4. Navigate the site and verify events align with [default mappings](/docs/frontend-integration/web-object/event-types#default-mapping-assignment-trigger) (for example `page` → `page_view`, `product` → `product_view`). ## Related [#related] * [Integration guide](/docs/frontend-integration/web-object/integration-guide) — choose GTM vs script vs npm * [Configuration](/docs/frontend-integration/web-object/configuration) * [Window object layer](/docs/frontend-integration/web-object/window-object) * [Integration patterns](/docs/frontend-integration/web-object/integration-patterns) * [Event types](/docs/frontend-integration/web-object/event-types) # Sophi Web Object (@usesophi/sophi-web-object) URL: /docs/frontend-integration/web-object The Sophi Web Object package is a framework-agnostic browser SDK that tracks e-commerce behaviour via window.sophi_object and sends structured events to the Sophi event API. The **Sophi Web Object** package is a framework-agnostic browser SDK that tracks e-commerce behaviour and sends structured events to the Sophi event API. It is separate from the [**Sophi Web SDK**](/docs/frontend-integration/sdk), which embeds the conversational assistant widget (`SophiWidget`). | Package | Purpose | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------- | | `@usesophi/sophi-web-object` | Data layer on `window.sophi_object`, automatic sends when tracked fields change, and `window.SophiTracker` for SPAs | | `@usesophi/sophi-web-sdk` | Widget UI, commerce events from the assistant, runtime methods on `SophiWidget` | Use **Web Object** when you need server-style tracking (page, product, basket, purchase, etc.) without depending on the widget bundle. If you are loading the **assistant widget** from GTM, follow [SDK GTM integration](/docs/frontend-integration/sdk/gtm-integration) (`@usesophi/sophi-web-sdk`).\ For **commerce / analytics** tracking with `window.sophi_object`, use **[GTM integration](/docs/frontend-integration/web-object/gtm-integration)** (`@usesophi/sophi-web-object`). ## Start here [#start-here] * **[GTM integration](/docs/frontend-integration/web-object/gtm-integration)** — Google Tag Manager setup (recommended for teams that want easy version upgrades via tag publishing). * **[Integration guide](/docs/frontend-integration/web-object/integration-guide)** — Compare GTM, script tag, and npm; best practices and a short events cheat sheet. * **[Installation](/docs/frontend-integration/web-object/installation)** — CDN URLs and package artifacts. ## How it works (high level) [#how-it-works-high-level] 1. You define `window.sophi_object` with a `config` block (at minimum `apiKey`) and optional context objects **before** the script runs, or you update them after load via assignments or `SophiTracker.push()`. 2. On `DOMContentLoaded`, the SDK starts: validates config, creates or restores a **client session** (cookies and server handshake), then watches `window.sophi_object` for changes to tracked fields. 3. Assignments to tracked keys (`page`, `user`, `product`, `basket`, `listing`, `transaction`) build **event payloads** and add them to an outbound queue. 4. The queue **debounces** sends (default 300 ms), **retries** on failure, and tries to flush remaining items when the visitor leaves the page. Events are delivered to **Sophi over HTTPS** (see [Configuration](/docs/frontend-integration/web-object/configuration) for the options you control). ## Reference [#reference] | Topic | Link | | --------------------------- | ------------------------------------------------------------------------------------- | | Configuration | [configuration.md](/docs/frontend-integration/web-object/configuration) | | `window.sophi_object` layer | [window-object.md](/docs/frontend-integration/web-object/window-object) | | SophiTracker API | [sophi-tracker.md](/docs/frontend-integration/web-object/sophi-tracker) | | Event types | [event-types.md](/docs/frontend-integration/web-object/event-types) | | Types reference | [types.md](/docs/frontend-integration/web-object/types) | | Data and auth flows | [flows.md](/docs/frontend-integration/web-object/flows) | | Validation | [validation.md](/docs/frontend-integration/web-object/validation) | | Integration patterns | [integration-patterns.md](/docs/frontend-integration/web-object/integration-patterns) | ## Builds [#builds] The package publishes: | Artifact | Format | | ------------------- | ---------------------------- | | `dist/sophi.js` | IIFE (`window.SophiTracker`) | | `dist/sophi.min.js` | Minified IIFE | | `dist/sophi.esm.js` | ES module | See [Installation](/docs/frontend-integration/web-object/installation) for CDN URLs and [Integration guide](/docs/frontend-integration/web-object/integration-guide) for how to load each variant. # Installation URL: /docs/frontend-integration/web-object/installation The npm package is @usesophi/sophi-web-object. Artifacts live under dist/ in the package. The npm package is **`@usesophi/sophi-web-object`**. Artifacts live under `dist/` in the package. | File | Use case | | -------------- | --------------------------------------------------- | | `sophi.min.js` | Production: ` ``` `async` is supported: the SDK waits for `DOMContentLoaded` (or runs immediately if the document is already loaded) before bootstrapping. CDN URLs and build artifacts are listed on [Installation](/docs/frontend-integration/web-object/installation). ## npm / ESM [#npm--esm] ```bash npm install @usesophi/sophi-web-object ``` ES module `import` is hoisted, so assign **`window.sophi_object` first**, then load the SDK with **dynamic import** (or a small separate entry file that runs before your main bundle): ```javascript window.sophi_object = { config: { apiKey: 'YOUR_API_KEY' }, }; await import('@usesophi/sophi-web-object/dist/sophi.esm.js'); // After bootstrap: window.SophiTracker and window.sophi_object ``` Alternatively, set `window.sophi_object` in an inline ` ``` This typically emits **`page_view`** (from `page`) and **`product_view`** (from `product`). ## SPA route change [#spa-route-change] ```javascript SophiTracker.push({ page: { type: 'Category', url: 'https://store.com/womens/dresses', category_id: 'cat-dresses' }, listing: { category_id: 'cat-dresses', name: "Women's Dresses", taxonomy: ['Clothing', 'Dresses'], total_results: 234, page_number: 1, page_size: 24, items: [ { product_id: '1627421', position: 1, name: 'Blue Dress', url: 'https://store.com/products/blue-dress', product_image_url: 'https://cdn.store.com/blue.jpg', currency: 'TRY', unit_price: 1200.0, unit_sale_price: 990.0, in_stock: 1 } ] } }); ``` ## Add to cart [#add-to-cart] Set **`event_type`** explicitly; include **`product`** and **`basket`**. ```javascript SophiTracker.push({ event_type: 'add_to_cart', page: { type: 'Product', url: 'https://store.com/products/blue-dress' }, product: { id: '1627421', category_ids: ['cat-dresses'], name: 'Blue Dress', url: 'https://store.com/products/blue-dress', product_image_url: 'https://cdn.store.com/blue.jpg', taxonomy: ['Clothing', 'Dresses'], currency: 'TRY', unit_price: 1200.0, unit_sale_price: 990.0, in_stock: 1 }, basket: { currency: 'TRY', total: 990.0, line_items: [ { product_id: '1627421', quantity: 1, currency: 'TRY', unit_price: 1200.0, unit_sale_price: 990.0, line_total: 990.0 } ] } }); ``` For **`remove_from_cart`**, use the same shape with an updated basket and `event_type: 'remove_from_cart'`. ## Purchase confirmation [#purchase-confirmation] ```javascript SophiTracker.push({ page: { type: 'Confirmation', url: 'https://store.com/checkout/confirmation/ORD-99231' }, transaction: { order_id: 'ORD-99231', currency: 'TRY', total: 1989.9, subtotal: 1980.0, tax: 0.0, shipping_cost: 9.9, discount_total: 210.0, coupon_code: 'SS25', payment_method: 'credit_card', line_items: [ { product_id: '1627421', quantity: 2, currency: 'TRY', unit_price: 1200.0, unit_sale_price: 990.0, line_total: 1980.0 } ] } }); ``` ## User login [#user-login] ```javascript SophiTracker.updateUser('usr-8842'); SophiTracker.push({ user: { consent: { gdpr_optin: true, version: '2026-01-15', timestamp: Math.floor(Date.now() / 1000) }, identity: { uuid: 'usr-8842', email: 'jane@example.com' }, profile: { name: 'Jane', surname: 'Doe', gender: 'F' }, behavior: { returning: true, has_transacted: true, transaction_count: 4 } } }); ``` ## User logout [#user-logout] ```javascript SophiTracker.updateUser(undefined); ``` Clears session state and establishes a new anonymous session for subsequent events. ## Related [#related] * [SophiTracker API](/docs/frontend-integration/web-object/sophi-tracker) * [Event types](/docs/frontend-integration/web-object/event-types) * [Validation](/docs/frontend-integration/web-object/validation) # SophiTracker API URL: /docs/frontend-integration/web-object/sophi-tracker After the bundle loads, window.SophiTracker exposes the public imperative API for SPAs, batched updates, login/logout events, and multi-object pushes such as add_to_cart. After the bundle loads, **`window.SophiTracker`** exposes the public imperative API. Use it for SPAs, batched updates, login/logout, and multi-object events (e.g. `add_to_cart` with both `product` and `basket`). ## `SophiTracker.push(partial)` [#sophitrackerpushpartial] ```typescript SophiTracker.push( partial: Partial> ): void ``` * Merges every key in `partial` into **`window.sophi_object`**. * Processes the update **once per watched key** present in `partial` (`page`, `user`, `product`, `basket`, `listing`, `transaction`), which may produce one event per key. * If `partial.event_type` is set, it overrides the default `event_type` **for all triggers** in that single `push` call. * If called before the SDK has finished starting, it no-ops and warns. Typical use: route changes, add-to-cart, or syncing several objects atomically. ## `SophiTracker.updateUser(userId)` [#sophitrackerupdateuseruserid] ```typescript SophiTracker.updateUser(userId: string | undefined): void ``` * Clears the current session (cookies and local session state). * Sets `config.userId` to the new value (or clears it when `undefined` / empty). * Establishes a **new session** with Sophi so subsequent events use fresh credentials. Call after your auth layer confirms **login** (pass stable external id) or **logout** (pass `undefined` for an anonymous session). ## `SophiTracker.setConsent(granted)` [#sophitrackersetconsentgranted] ```typescript SophiTracker.setConsent(granted: boolean): void ``` Grants or withdraws storage consent at runtime. Use it together with `config.storageConsent: false` to run the Web Object in [Cookie Consent Management](/docs/frontend-integration/cookie-consent). * `setConsent(true)` — activates the previously passive Web Object: it creates a session, writes cookies, and starts sending events (including the current `sophi_object` snapshot). * `setConsent(false)` — makes it passive again and clears the session cookies. ```javascript // Visitor accepted cookies in your consent banner window.SophiTracker.setConsent(true); ``` Until `setConsent(true)` is called (or `config.storageConsent` is `true`), the Web Object sends **no events**. `push(...)` calls are dropped while passive. ## `SophiTracker.getSession()` [#sophitrackergetsession] ```typescript SophiTracker.getSession(): Promise ``` Returns a promise for the SDK’s active session object. Intended for **debugging or advanced integrations**; normal storefront tracking does not require calling this. ## Related [#related] * [Configuration](/docs/frontend-integration/web-object/configuration) — how `userId` participates in session creation * [Integration patterns](/docs/frontend-integration/web-object/integration-patterns) — login and SPA examples * [Data and auth flows](/docs/frontend-integration/web-object/flows) # Types reference URL: /docs/frontend-integration/web-object/types TypeScript type definitions for all Sophi Web Object data structures including PageObject, UserObject, ProductObject, BasketObject, and EventEnvelope. Field names in JSON payloads use **`snake_case`**. The snippets below match the TypeScript types in `@usesophi/sophi-web-object`. ## `SophiConfig` and `SophiSession` [#sophiconfig-and-sophisession] Set only the fields described in [Configuration](/docs/frontend-integration/web-object/configuration) on `window.sophi_object.config`. The published `SophiConfig` type in the npm package may include additional properties that the SDK fills in; you should not set those yourself. ```typescript interface SophiSession { accessToken: string; clientId: string; } ``` ## `PageType` and `PageObject` [#pagetype-and-pageobject] ```typescript type PageType = | 'Home' | 'Category' | 'Product' | 'Basket' | 'Checkout' | 'Confirmation' | 'Search' | 'Account' | 'Custom'; interface PageObject { type: PageType; url: string; title?: string; language?: string; category_id?: string; search_query?: string; custom?: Record; } ``` ## `UserObject` and sub-objects [#userobject-and-sub-objects] `user` events require **`consent`** with **`gdpr_optin`** defined. When `gdpr_optin !== true`, the SDK **strips** `identity` and `profile` from the outgoing envelope (non-PII `behavior`, `segmentation`, `custom` remain). Do not rely on this as your only privacy control. ```typescript interface ConsentObject { gdpr_optin: boolean; version: string; timestamp: number; // epoch seconds } interface IdentityObject { uuid?: string; email?: string; phone_number?: string; // E.164, e.g. "+447700900000" } interface ProfileObject { name?: string; surname?: string; gender?: 'F' | 'M' | 'O' | 'U'; birthday?: string; // YYYY-MM-DD language?: string; // BCP-47 } interface BehaviorObject { returning?: boolean; has_transacted?: boolean; transaction_count?: number; last_transaction_at?: number; // epoch seconds } interface SegmentationObject { static_segment_ids?: (string | number)[]; } interface UserObject { consent: ConsentObject; identity?: IdentityObject; profile?: ProfileObject; behavior?: BehaviorObject; segmentation?: SegmentationObject; custom?: Record; } ``` ## `ProductObject` [#productobject] ```typescript interface ProductObject { id: string; variant_id?: string; sku?: string; group_code?: string; category_ids: string[]; name: string; url: string; product_image_url: string; taxonomy: string[]; currency: string; unit_price: number; unit_sale_price: number; omnibus_price?: number; in_stock: 0 | 1; stock?: number; brand?: string; color?: string; size?: string; gender?: string; locale?: string; description?: string; tags?: string[]; custom?: Record; } ``` ## `BasketObject` and `BasketLineItem` [#basketobject-and-basketlineitem] Line items are **flat** rows (not nested full `product` objects). ```typescript interface BasketLineItem { product_id: string; variant_id?: string; sku?: string; quantity: number; currency: string; unit_price: number; unit_sale_price: number; line_total: number; } interface BasketObject { basket_id?: string; currency: string; total: number; subtotal?: number; tax?: number; shipping_cost?: number; discount_total?: number; coupon_code?: string; line_items: BasketLineItem[]; } ``` ## `ListingObject` and `ListingItemObject` [#listingobject-and-listingitemobject] At least one of **`category_id`**, **`slug`**, or **`search_query`** must be present on `ListingObject`. ```typescript interface ListingItemObject { product_id: string; variant_id?: string; position: number; name: string; url: string; product_image_url: string; currency: string; unit_price: number; unit_sale_price: number; in_stock?: 0 | 1; brand?: string; } interface ListingObject { category_id?: string; slug?: string; search_query?: string; name?: string; taxonomy?: string[]; total_results?: number; page_number?: number; page_size?: number; sort?: string; applied_filters?: Record; items?: ListingItemObject[]; } ``` ## `TransactionObject` and `TransactionLineItemObject` [#transactionobject-and-transactionlineitemobject] ```typescript interface TransactionLineItemObject { product_id: string; variant_id?: string; sku?: string; quantity: number; currency: string; unit_price: number; unit_sale_price: number; line_total: number; } interface TransactionObject { order_id: string; currency: string; total: number; subtotal?: number; tax?: number; shipping_cost?: number; discount_total?: number; coupon_code?: string; payment_method?: string; line_items: TransactionLineItemObject[]; } ``` ## `EventEnvelope` [#eventenvelope] Payload sent to **Sophi** (HTTPS, JSON) for each tracked event: ```typescript type ExternalEventType = | 'page_view' | 'identify' | 'product_view' | 'listing_view' | 'add_to_cart' | 'remove_from_cart' | 'basket_view' | 'checkout_started' | 'purchase' | 'custom'; interface EventEnvelope { event_type: ExternalEventType; client_timestamp: number; // epoch seconds sdk_version: string; page: PageObject; user?: UserObject; product?: ProductObject; basket?: BasketObject; listing?: ListingObject; transaction?: TransactionObject; custom?: Record; } ``` ## Conventions (summary) [#conventions-summary] | Topic | Rule | | ----------------- | ---------------------------------------------------------------------------- | | Timestamps | Unix epoch **seconds** (integers) on consent and envelope `client_timestamp` | | Currency | ISO 4217 (`TRY`, `EUR`, …) | | Language | BCP-47 (`tr-TR`, `en-US`) | | `in_stock` | Integer `0` or `1` | | `unit_sale_price` | Always send; equals `unit_price` when there is no discount | | `line_total` | Caller-computed: typically `unit_sale_price * quantity` | | Unknown fields | Prefer omitting keys over sending placeholder `null` / `0` / `""` | Full validation rules: [Validation](/docs/frontend-integration/web-object/validation). # Validation URL: /docs/frontend-integration/web-object/validation Before an event is sent, @usesophi/sophi-web-object runs client-side field validation; failures log a console.warn and the event is skipped without any network request. Before an event is sent, the SDK runs **client-side checks** on the relevant fields. Failures log **`console.warn`** and that event is **skipped** (nothing is sent). ## Rules [#rules] | Rule | Applies to | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | | `page.type` and `page.url` required | `page_view` / any envelope needing `page` | | `user.consent` present; `user.consent.gdpr_optin` defined | `identify` | | `product.id`, non-empty `product.category_ids`, and required display/pricing fields | Product-related events | | `basket.total` number, `basket.currency`, non-empty `basket.line_items`; each line: `product_id`, `currency`, numeric `line_total`, `quantity >= 1` | Basket-related events | | At least one of `listing.category_id`, `listing.slug`, `listing.search_query` | `listing_view` | | `transaction.order_id`, `transaction.currency`, numeric `transaction.total`, non-empty `transaction.line_items`; each line: `product_id`, `currency`, `line_total`, `quantity >= 1` | `purchase` | | `sophi_object.page` present when building an event | Any send | For **product** payloads, required fields also include: `name`, `url`, `product_image_url`, `taxonomy`, `currency`, `unit_price`, `unit_sale_price`, `in_stock` (in addition to `id` and `category_ids`). ## Conventions [#conventions] | Convention | Detail | | --------------------- | ---------------------------------------------------------- | | Field names | `snake_case` in JSON | | Timestamps | Epoch **seconds** (integer) | | Currency | ISO 4217 | | Language / locale | BCP-47 | | Phone | E.164 with leading `+` | | IDs | Your source-system external IDs, not internal Sophi DB ids | | `custom` bags | Optional `Record` on each object type | | `event_type` override | `sophi_object.event_type` or `push({ event_type, ... })` | ## Related [#related] * [Types reference](/docs/frontend-integration/web-object/types) * [Event types](/docs/frontend-integration/web-object/event-types) # The window.sophi_object layer URL: /docs/frontend-integration/web-object/window-object The global window.sophi_object follows the SophiObjectShape — a config block, an optional event-type override, and optional context objects (page, user, product, basket, listing, transaction) that trigger event collection when assigned. The global **`window.sophi_object`** follows the **`SophiObjectShape`**: a config block, an optional event-type override, and optional context objects for the current page. ## Shape [#shape] Conceptually: ```typescript interface SophiObjectShape { config?: SophiConfig; event_type?: ExternalEventType; page?: PageObject; user?: UserObject; product?: ProductObject; basket?: BasketObject; listing?: ListingObject; transaction?: TransactionObject; } ``` Full field definitions: [Types reference](/docs/frontend-integration/web-object/types). ## Watched keys (Proxy) [#watched-keys-proxy] After startup, `window.sophi_object` is wrapped so that assignments to these keys **trigger event processing** (build payload → queue for send): * `page` * `user` * `product` * `basket` * `listing` * `transaction` **Not watched** (no collection on write): * `config` * `event_type` Values are written to the object **before** events are derived, so each send reflects the latest state. ## Default `event_type` per key [#default-event_type-per-key] When `event_type` is not set on `sophi_object` for that update, the SDK uses: | Key assigned | Default `event_type` | | ------------- | -------------------- | | `page` | `page_view` | | `user` | `identify` | | `product` | `product_view` | | `basket` | `basket_view` | | `listing` | `listing_view` | | `transaction` | `purchase` | Override by setting `sophi_object.event_type` **before** the assignment that triggers collection, or by passing `event_type` in [`SophiTracker.push()`](/docs/frontend-integration/web-object/sophi-tracker). ## Initial page load [#initial-page-load] If you set multiple objects on `sophi_object` **before** the script loads, startup processing runs **once per present watched key**, so you may get several events (each may use the same or different resolved `event_type` depending on overrides). ## `page` is required for events [#page-is-required-for-events] The SDK builds each event from the current `sophi_object`. **`page` must be present** (with valid `type` and `url`) for events to be sent; otherwise that update produces no event. ## Related [#related] * [Event types](/docs/frontend-integration/web-object/event-types) * [SophiTracker API](/docs/frontend-integration/web-object/sophi-tracker) * [Types reference](/docs/frontend-integration/web-object/types)