# Welcome to EdgeTag

## Overview

EdgeTag isn't another CDP trying to be everything to everyone. We're the server-side tracking infrastructure that captures, cleans, and activates the first-party signals your marketing platforms need to scale. Implemented in days, not months, with zero engineering required.&#x20;

Before you get started with EdgeTag, we recommend you understand How Blotout's EdgeTag works and why it should be your choice as a performance marketer.&#x20;

{% content-ref url="/pages/9GPkXJOfP7TkqaX6p2mO" %}
[Why EdgeTag?](/overview/why-edgetag)
{% endcontent-ref %}

{% content-ref url="/pages/2PzsEz6tIg8jTbM0uOst" %}
[How it works?](/overview/how-it-works)
{% endcontent-ref %}

## Get Started

Want to get started with EdgeTag? We offer various paths based on the platforms you use and your coding skills. EdgeTag supports developer communities with pro-code integrations in place and ready-to-install apps on major E-commerce platforms.&#x20;

Follow our guides and choose the setup that best suits your business. Please refer to our [Basic Setup Guide](#get-started) first to get started with your path.

### Ways to Get Started with EdgeTag&#x20;

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-cover data-type="image">Cover image</th></tr></thead><tbody><tr><td><a href="/pages/htgd5mi4jqCnwQ4ZPMpe">No Code</a></td><td>EdgeTag supports No-Code installation on various platforms. </td><td><a href="/files/aMDyjanxpBRYODsNhEMB">/files/aMDyjanxpBRYODsNhEMB</a></td></tr><tr><td><a href="https://docs.edgetag.io/onboarding">Low Code</a></td><td>Customize EdgeTag implementations  on your Landing pages </td><td><a href="/files/fvJHm4rh0qFBDaE1Oe1z">/files/fvJHm4rh0qFBDaE1Oe1z</a></td></tr><tr><td><a href="/pages/jXUpTwsppqY8YUlG4bd7">Pro Code</a></td><td>Get started with our APIs and SDKs</td><td><a href="/files/JOqUawYPxbeFFcey3WKZ">/files/JOqUawYPxbeFFcey3WKZ</a></td></tr></tbody></table>

## Developer's Guide to EdgeTag&#x20;

<table data-card-size="large" data-view="cards"><thead><tr><th align="center"></th><th data-hidden data-card-cover data-type="image">Cover image</th></tr></thead><tbody><tr><td align="center"><a href="/spaces/J61ZiAn9bOjRfi7zlGtO"><strong>API Documentation</strong></a></td><td><a href="/files/0UWAph7tdXbTOKHsUIz2">/files/0UWAph7tdXbTOKHsUIz2</a></td></tr><tr><td align="center"><a href="/pages/4EuMCZlyvQRG5WFTvwSr"><strong>Managed vs Self-Hosting</strong></a> </td><td><a href="/files/vOuvrn9ES0k0rl2KVqix">/files/vOuvrn9ES0k0rl2KVqix</a></td></tr><tr><td align="center"><a href="/pages/jXUpTwsppqY8YUlG4bd7"><strong>Implementation Options</strong> </a></td><td><a href="/files/VdQ7ADbWoA33ZJfUVCoV">/files/VdQ7ADbWoA33ZJfUVCoV</a></td></tr><tr><td align="center"><a href="/pages/xc7IQAo8d9Jjb659e5ys"><strong>CRM Integration with EdgeTag</strong></a></td><td><a href="/files/GktEcVNxXP1Ng5Z4ORiu">/files/GktEcVNxXP1Ng5Z4ORiu</a></td></tr><tr><td align="center"><a href="/pages/yKU4DCnR2EImwcQ0mYYU"><strong>Webhook Integrations</strong></a></td><td><a href="/files/mbtkKYkzdBnYW3lwRSFf">/files/mbtkKYkzdBnYW3lwRSFf</a></td></tr><tr><td align="center"><a href="/pages/iy9kxXW4nJStMw39BGNq"><strong>Standard Events on EdgeTag</strong></a></td><td><a href="/files/Wxneo2rbKDLcNDcak6Ez">/files/Wxneo2rbKDLcNDcak6Ez</a></td></tr></tbody></table>

## Marketer's Guide to EdgeTag

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th data-hidden data-card-cover data-type="image">Cover image</th></tr></thead><tbody><tr><td><a href="/spaces/x66G772sQxH3gFPolQj2"><strong>Onboarding EdgeTag on your Eco-System</strong> </a></td><td><a href="/files/XayQXS3UwmEOL6BTksK6">/files/XayQXS3UwmEOL6BTksK6</a></td></tr><tr><td><a href="/spaces/x66G772sQxH3gFPolQj2"><strong>Scaling with EdgeTag</strong> </a></td><td><a href="/files/NTzZy7z9hI4CKR6GnumG">/files/NTzZy7z9hI4CKR6GnumG</a></td></tr><tr><td><a href="/spaces/d24Ahwiz1U3cDBeQ96Ey"><strong>EdgeTag features</strong> </a></td><td><a href="/files/z6c49IXaig8R6dWPzQFn">/files/z6c49IXaig8R6dWPzQFn</a></td></tr><tr><td><a href="/spaces/4yRxUEoA5DhOnwWSuQoM"><strong>FAQs / How-Tos</strong></a></td><td><a href="/files/JG76FNGMKI0Y95tbmsu8">/files/JG76FNGMKI0Y95tbmsu8</a></td></tr></tbody></table>


# Why EdgeTag?

### **The Privacy-First Internet**

The internet has shifted to privacy-first by default. Apple enforces strict privacy controls, browsers block third-party cookies, and regulations such as GDPR and CCPA require granular consent management. For growth marketers, this means the customer data you rely on for attribution, targeting, and measurement is increasingly unreliable.

Traditional browser-based tracking faces critical challenges: cookie deprecation limits attribution accuracy, ad blockers prevent data collection regardless of user consent, and privacy regulations require verifiable proof that consent choices are honored across every destination. These limitations have made server-side infrastructure essential for reliable data collection.

### **Beyond Server-Side: The Intelligence Layer Imperative**

Server-side infrastructure solves data reliability, but that's table stakes. To actually improve marketing performance in the privacy-first era, you need infrastructure that goes beyond basic server-side tracking to address three critical challenges:

**Persistent Identity Beyond Browser Limitations**

Browser cookies expire. Safari caps them at 7 days; other browsers impose similar restrictions. Without persistent identity resolution, your attribution windows remain limited, and ad platforms lose conversion signals from customers who convert after cookie expiration. You need an identity graph that maintains customer profiles indefinitely, regardless of browser cookie policies, to preserve attribution accuracy and deliver complete conversion signals to your marketing platforms.

**Real-Time Data Intelligence for Ad Platform Optimization**

Ad platforms like Meta and Google optimize based on the conversion signals they receive. Raw event data, even when reliably delivered server-side, isn't enough. Platforms need enriched signals with first-party data, custom conversion events tailored to your business goals (new customer acquisition, high-margin products), and optimized data quality to improve algorithm performance. Without real-time event transformation, you're sending basic signals while competitors send intelligent, enriched data that drives better ROAS.

**Future-Proof Architecture for Zero-Maintenance Integration**

Marketing platforms constantly change API specifications, data requirements, and signal expectations. Today's platforms demand complex hybrid implementations that combine browser-side pixels with server-side APIs, each requiring distinct data formats and synchronization logic. You need a done-for-you infrastructure that stays up to date with platform changes while eliminating engineering maintenance.

EdgeTag is a purpose-built first-party data infrastructure that optimizes every signal, powers every decision, and adapts automatically to the evolving privacy-first landscape.

<br>


# How it works?

### Overview

EdgeTag is a purpose-built first-party data infrastructure that optimizes every signal, powers every decision, and adapts automatically to the evolving privacy-first landscape. The platform delivers:

**Complete Zero-Code Data Infrastructure**

Automatic data capture, scalable ID graph, and real-time streaming to 50+ marketing and analytics channels, with built-in consent management, observability, and bot protection.

**Single Tenant Architecture for Enterprise Security**

Dedicated infrastructure where no resources are shared between customers, ensuring complete data isolation, predictable performance, and built-in GDPR/CCPA compliance with full audit trails.

**Service as Software with Done-For-You Implementation**

Zero-code deployment, managed infrastructure, and expert configuration—eliminating the need to hire teams for server-side implementation so you can focus on growth, not data maintenance.

<br>


# How EdgeTag Works

<figure><img src="/files/4VbYBL3THiDpSMsFEuEx" alt=""><figcaption></figcaption></figure>

### **Event Collection from your Digital Front**

EdgeTag deploys a lightweight collector on your website that captures standard events and sends them to your first-party server infrastructure for processing. EdgeTag supports no-code installs on the following platforms:

* Shopify: App install, no code required (10 minutes)
* BigCommerce: App install, no code required (10 minutes)
* WooCommerce: Plugin install, no code required (15 minutes)
* Salesforce Commerce Cloud: App install, no code required (30 minutes)
* WordPress: Plugin install (15 minutes)

EdgeTag also offers JavaScript and HTTP SDKs for React frameworks and custom storefronts to easily capture data.

### **Core Processing: ID and Transform**

Once event data reaches EdgeTag, two critical functions happen:

#### **ID Graph (Identity Resolution)**

EdgeTag stitches data into and from a persistent identity graph. This ID graph creates and maintains a persistent user ID per browser that survives cookie deletion, enabling you to track customer journeys across sessions. When a customer identifies themselves (via email or phone), EdgeTag instantly connects their anonymous browsing history to their known profile.

#### **Data Transformation**

EdgeTag helps transform standard events into intelligent marketing signals that improve algorithm performance and ROAS. EdgeTag connects via out-of-the-box integrations with CRMs like Shopify or receives data via webhooks from other data sources.&#x20;

This enables real-time enrichment, allowing EdgeTag to create custom conversion signals tailored to your specific business goals, such as optimizing for new customer acquisition (vs. returning customers), specific product categories, margins over revenue, and more.

These intelligent signals are then formatted into platform-specific schemas (Meta CAPI, Google Enhanced Conversions, TikTok Events API, etc.).

#### Data Delivery: Stream to 50+ Marketing Channels

EdgeTag enables both browser-side and server-side event delivery to your entire marketing and analytics ecosystem. This hybrid approach, leveraging the unified data layer of EdgeTag, ensures maximum data capture, browser-side events for immediate pixel firing, and server-side delivery via APIs for reliability and completeness. EdgeTag supports signal delivery to the following popular channels:

**Advertising Channels:** Meta, Google Ads, TikTok, Pinterest, Snapchat, AppLovin, LinkedIn, Reddit, Bing, and more.

**Retention Marketing Channels:** Klaviyo, Attentive, Postscript, Emotive, Mailchimp, Yotpo, and other email/SMS providers.

**Analytics & Warehouse:** Google Analytics 4, Amplitude, Mixpanel, Google Cloud PubSub, S3 buckets, and more.

<table data-view="cards"><thead><tr><th align="center"></th><th align="center"></th></tr></thead><tbody><tr><td align="center">Advertising Channels</td><td align="center">Meta, Google Ads, TikTok, Pinterest, Snapchat, AppLovin, LinkedIn, Reddit, Bing and more.</td></tr><tr><td align="center">Retention Marketing Channels</td><td align="center">Klaviyo, Attentive, Postscript, Emotive, Mailchimp, Yotpo, and other email/SMS providers.</td></tr><tr><td align="center">Analytics &#x26; Warehouse</td><td align="center">Google Analytics 4, Amplitude, Mixpanel, Google Cloud PubSub, S3 buckets and more.</td></tr></tbody></table>

All data delivery happens in parallel with sub-second latency, ensuring every platform receives complete, accurate customer data simultaneously.

<br>


# Other Built-In Capabilities

### **Consent Management & Compliance**

EdgeTag integrates with your existing consent platform (OneTrust, Shopify, CookieBot, etc.) and enforces customer consent choices in real-time before any data is shared. Granular controls allow customers to consent to specific channels, with decisions automatically propagated across all 50+ destinations.&#x20;

Built-in DSAR automation handles data subject access requests with self-service deletion across BrowserDB and all connected platforms, maintaining full audit trails for GDPR, CCPA, and HIPAA compliance.

### **Enterprise Observability & Real-Time Monitoring**

EdgeTag provides complete pipeline visibility with live dashboards showing data throughput, delivery success rates, and errors up to the last minute. ML-powered anomaly detection alerts you when traffic drops or delivery fails.&#x20;

Real-time debugging tools include live log streaming and event validators, allowing you to see exactly which data was sent to each platform and troubleshoot issues immediately.

### **Bot Protection**

Every EdgeTag deployment runs behind Cloudflare's Web Application Firewall, which provides enterprise-level threat detection. Machine learning identifies and blocks malicious traffic patterns before they corrupt your customer data with negative signals. This ensures only validated interactions reach your marketing tools.

<br>


# Getting Started

Starting with EdgeTag is really straightforward. You will need to enter your business email, and the system will guide you through the setup step by step. You can then add your teammates once the setup is complete.

However, you need the following access to get the setup entirely done;

1. **Access to your DNS provider:** This is where you add DNS records for the website you want to configure. This step is necessary to verify that you are the first party on the internet for your setup.
2. **Channel credentials:** For example, if you want to add Meta, you need a pixel ID and permission to authenticate the dataset, which is available upon setup.
3. **Access to your website code**: Ultimately, you will need to add snippet code and desired events to your site.

Now that you have the essential things ready, let's go through the onboarding process. You can complete it in under 15 minutes if you have all the necessary aspects readily available.

### Platform <a href="#platform" id="platform"></a>

In the first step, you are required to select the platform for your site, such as Shopify, WooCommerce, or another platform.

<figure><img src="/files/M6B6YGELYxraCKjeg1SH" alt=""><figcaption></figcaption></figure>

### Domain <a href="#domain" id="domain"></a>

In the next step, please enter your site's domain. If your site is, let's say, *<https://www.mysite.com>*, enter mysite.com.&#x20;

{% hint style="warning" %}
Remove 'https\://' and '[www](http://www).' from the domain.
{% endhint %}

Once you enter it, click 'Verify Domain'. Our system will verify the domain's validity and create a first-party subdomain, providing you with access to the EdgeTag system.

<figure><img src="/files/bB01CLyUc7a2iINJ2c25" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
Ensure you have entered the correct domain. It is critical to get it right.
{% endhint %}

### DNS

Once you have entered and verified your domain, you will receive two DNS records (one TXT record and one CNAME record) that must be added to your DNS to certify your setup as first-party.

The TXT record is necessary to verify ownership of the domain that you entered in the previous step.

{% hint style="info" %}
Some providers insist that they have the domain attached to the TXT record, while others don't. For the ones who do, add the domain to the name of the TXT record. (in our screenshot, we would need to *change \_cf-custom-hostname.ounvr* to *\_cf-custom-hostname.ounvr.mysite.com*)
{% endhint %}

<figure><img src="/files/LpeqlL6u02wWhp6ROC5n" alt=""><figcaption></figcaption></figure>

Once you have added the TXT record, click 'Next record'. We will then see a CNAME record, which we need to add. This record will map your newly created first-party subdomain to the edge and create a certificate for your.

<figure><img src="/files/4NwnqDgi2lHbhCxO1uFO" alt=""><figcaption></figcaption></figure>

Click on 'Verify Records' after adding both records, and you should see a green box indicating that everything looks great.

{% hint style="info" %}
Some providers need longer to propagate DNS records. You can click skip if you do not see a green box (and you added the records). It will get verified automatically.
{% endhint %}

<figure><img src="/files/ShlhbwfbRVbD12YTiCqx" alt=""><figcaption></figcaption></figure>

### Consent <a href="#consent" id="consent"></a>

Consent is crucial for us, as we respect users' privacy and are committed to maintaining it. This is also one of the main reasons why we created this product. All events sent from our system are guarded by consent, so we don't send any data without permission.

{% hint style="info" %}
You can always enable consent later on inside our app.
{% endhint %}

<figure><img src="/files/IlRRUwx5OuuNHsIfHuqw" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/eCpXMU2mjlt9iDsFDKfE" alt=""><figcaption></figcaption></figure>

### Channels <a href="#provider" id="provider"></a>

In the next step of the onboarding process, we will select the channels you want to add. You will still have the option to add channels later from the dashboard if you wish to do so.

In this case, I will select the Pinterest channel that I added during the onboarding process.

<figure><img src="/files/S3Xnb57FqCiQ7w0Jxxsr" alt=""><figcaption></figcaption></figure>

Now, we need to enter the details to configure all the channels we selected earlier (Pinterest, in my case). Once added, click 'Save' to save the channel configuration.

<figure><img src="/files/RqDgM1gtwqAxxebeHQja" alt=""><figcaption></figcaption></figure>

### Billing Details <a href="#billing" id="billing"></a>

You must enter your credit card details before deployment happens. Without a credit card, deployment is not allowed.

$49.99/month includes one domain and 100K API calls (Events). You get a free trial of 30 days.

<figure><img src="/files/ESD9HNGR7AJIkufIMVkN" alt=""><figcaption></figcaption></figure>

### Deployment

Now that everything is ready, we will begin setting up your environment. After a short wait, you will see one of the following screens, depending on the platform you chose. This screen will explain the final step: adding our snippet to your site.

#### BigCommerce

<figure><img src="/files/ZAnWpAmo6Xq4ctQ4ycWX" alt=""><figcaption></figcaption></figure>

#### WooCommerce

<figure><img src="/files/uALtj9Cxl1ZWWXxNgEps" alt=""><figcaption></figcaption></figure>

#### Salesforce

<figure><img src="/files/rtBPIvrT4r8TqMYBvsaY" alt=""><figcaption></figcaption></figure>

#### Others

<figure><img src="/files/cDSn5KnrH6FuddsBLJOK" alt=""><figcaption></figcaption></figure>


# Standard Events

With the EdgeTag platform, we aim to minimize friction for both customers and developers as much as possible.&#x20;

With that in mind, we created standard events that support the idea of writing code once, and then you can add as many channels/apps/plugins as you would like without changes to payloads. All channels receive the same payload, and we perform transformations for you both on the browser and the server in real-time. This also allows us to make sure that when a channel introduces a change, we handle it for you.

All parameters are optional, except those marked as required. We will generate an `eventId` automatically for you.

You can also add additional properties to the standard payload, but keep in mind that some channels will not support it.

{% hint style="warning" %}
The following examples are provided for demonstration purposes only, illustrating how the payload should appear. You need to **REPLACE** example data with dynamic values from your website.
{% endhint %}

### PageView

This is the default pixel tracking page for visits.

```javascript
edgetag('tag', 'PageView')
```

### ViewContent

A visit to a web page you care about (for example, a product page or landing page). ViewContent tells you if someone visits a web page's URL, but not what they see or do on that page.

| Name     | Type                     | Required | Description                                                                                                 |
| -------- | ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------- |
| currency | string                   | no       | The currency for the value specified                                                                        |
| value    | number                   | no       | A numeric value associated with this event. This could be a monetary value or a value in some other metric. |
| contents | [Content\[\]](#contents) | no       | A list of products or items related to this event                                                           |

```javascript
edgetag('tag', 'ViewContent', {
  currency: 'USD',
  value: 10.50,
  contents: [{
    id: '123123123',
    quantity: 1,
    item_price: 10.50,
    title: 'Summer Fun',
    category: 'bracelets',
    image: 'https://mysite.com/product/fun-main.jpg',
    url: 'https://mysite.com/summer-fun'
  }]
})
```

### AddToCart

When a product is added to the shopping cart.

| Name        | Type                     | Required | Description                                                                                                 |
| ----------- | ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------- |
| currency    | string                   | Yes      | The currency for the value specified                                                                        |
| value       | number                   | Yes      | A numeric value associated with this event. This could be a monetary value or a value in some other metric. |
| contents    | [Content\[\]](#contents) | No       | A list of products or items related to this event                                                           |
| checkoutUrl | string                   | No       | Url to go to the checkout page                                                                              |

```javascript
edgetag('tag', 'AddToCart', {
  currency: 'USD',
  value: 10.50,
  checkoutUrl: 'http://www.example.com/path/to/checkout',
  contents: [{
    id: '123123123',
    quantity: 1,
    item_price: 10.50,
    title: 'Summer Fun',
    category: 'bracelets',
    image: 'https://mysite.com/product/fun-main.jpg',
    url: 'https://mysite.com/summer-fun'
  }]
})
```

### RemoveFromCart

When a product is removed from the shopping cart.

| Name        | Type                     | Required | Description                                                                                                 |
| ----------- | ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------- |
| currency    | string                   | Yes      | The currency for the value specified                                                                        |
| value       | number                   | Yes      | A numeric value associated with this event. This could be a monetary value or a value in some other metric. |
| contents    | [Content\[\]](#contents) | No       | A list of products or items related to this event                                                           |
| checkoutUrl | string                   | No       | Url to go to the checkout page                                                                              |

```javascript
edgetag('tag', 'RemoveFromCart', {
  currency: 'USD',
  value: 10.50,
  checkoutUrl: 'http://www.example.com/path/to/checkout',
  contents: [{
    id: '123123123',
    quantity: 1,
    item_price: 10.50,
    title: 'Summer Fun',
    category: 'bracelets',
    image: 'https://mysite.com/product/fun-main.jpg',
    url: 'https://mysite.com/summer-fun'
  }]
})

```

### InitiateCheckout

When a person enters the checkout flow before completing the checkout process.

| Name        | Type                     | Required | Description                                                                                                 |
| ----------- | ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------- |
| currency    | string                   | Yes      | The currency for the value specified                                                                        |
| value       | number                   | Yes      | A numeric value associated with this event. This could be a monetary value or a value in some other metric. |
| contents    | [Content\[\]](#contents) | No       | A list of products or items related to this event                                                           |
| checkoutUrl | string                   | No       | Url to go to the checkout page                                                                              |

```javascript
edgetag('tag', 'InitiateCheckout', {
  currency: 'USD',
  value: 20.50,
  checkoutUrl: 'http://www.example.com/path/to/checkout',
  contents: [
    {
      id: '123123123',
      quantity: 1,
      item_price: 10.50,
      title: 'Summer Fun',
      category: 'bracelets',
      image: 'https://mysite.com/product/fun-main.jpg',
      url: 'https://mysite.com/summer-fun'
    },
    {
      id: '4423434343',
      quantity: 2,
      item_price: 5,
      title: 'Summer Shorts',
      category: 'shorts',
      image: 'https://mysite.com/product/shorts-main.jpg',
      url: 'https://mysite.com/summer-shorts'
    }
  ]
})
```

### AddShippingInfo

When the user has submitted their shipping information.

| Name     | Type                      | Required | Description                                                                                                 |
| -------- | ------------------------- | -------- | ----------------------------------------------------------------------------------------------------------- |
| currency | string                    | Yes      | The currency for the value specified                                                                        |
| value    | number                    | Yes      | A numeric value associated with this event. This could be a monetary value or a value in some other metric. |
| contents | [Contents\[\]](#contents) | No       | A list of products or items related to this event                                                           |

```javascript
edgetag('tag', 'AddShippingInfo', {
  currency: 'USD',
  value: 20.50,
  contents: [
    {
      id: '123123123',
      quantity: 1,
      item_price: 10.50,
      title: 'Summer Fun',
      category: 'bracelets',
      image: 'https://mysite.com/product/fun-main.jpg',
      url: 'https://mysite.com/summer-fun'
    },
    {
      id: '4423434343',
      quantity: 2,
      item_price: 5,
      title: 'Summer Shorts',
      category: 'shorts',
      image: 'https://mysite.com/product/shorts-main.jpg',
      url: 'https://mysite.com/summer-shorts'
    }
  ]
})
```

### AddPaymentInfo

When payment information is added to the checkout flow.

| Name     | Type                     | Required | Description                                                                                                 |
| -------- | ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------- |
| currency | string                   | Yes      | The currency for the value specified                                                                        |
| value    | number                   | Yes      | A numeric value associated with this event. This could be a monetary value or a value in some other metric. |
| contents | [Content\[\]](#contents) | No       | A list of products or items related to this event                                                           |

```javascript
edgetag('tag', 'AddPaymentInfo', {
  currency: 'USD',
  value: 20.50,
  contents: [
    {
      id: '123123123',
      quantity: 1,
      item_price: 10.50,
      title: 'Summer Fun',
      category: 'bracelets',
      image: 'https://mysite.com/product/fun-main.jpg',
      url: 'https://mysite.com/summer-fun'
    },
    {
      id: '4423434343',
      quantity: 2,
      item_price: 5,
      title: 'Summer Shorts',
      category: 'shorts',
      image: 'https://mysite.com/product/shorts-main.jpg',
      url: 'https://mysite.com/summer-shorts'
    }
  ]
})
```

### Purchase

When a purchase is made or the checkout flow is completed.

{% hint style="info" %}
We suggest that for the Purchase event, you provide `eventId` which matches `orderId`. This way, if you are sending Purchase events from any other system, channels can de-duplicate them.
{% endhint %}

| Name         | Type                      | Required | Description                                                                                                  |
| ------------ | ------------------------- | -------- | ------------------------------------------------------------------------------------------------------------ |
| currency     | string                    | Yes      | The currency for the value specified                                                                         |
| value        | number                    | Yes      | A numeric value associated with this event. This could be a monetary value or a value in some other metric.  |
| contents     | [Content\[\]](#contents)  | No       | A list of products or items related to this event                                                            |
| orderId      | string                    | Yes      | The order ID for the transaction.                                                                            |
| discounts    | [Discount\[\]](#discount) | No       | The discount codes utilized for the transaction                                                              |
| grossValue   | number                    | No       | A number representing the gross value for the value metric. Used for channels configured to use gross value. |
| taxCost      | number                    | No       | A number representing the tax cost for the value metric. Used to calculate net value if configured.          |
| shippingCost | number                    | No       | A number representing the shipping cost for the value metric. Used to calculate net value if configured.     |

```javascript
edgetag('tag', 'Purchase', {
  currency: 'USD',
  value: 20.50,
  orderId: '190315',
  eventId: '190315',
  discounts: [
    {
      code: 'OFF20',
      value: '20',
      type: 'PERCENTAGE'
    },
    {
      code: 'BONUS150',
      value: '150',
      type: 'FLAT'
    }
  ],
  contents: [
    {
      id: '123123123',
      quantity: 1,
      item_price: 10.50,
      title: 'Summer Fun',
      category: 'bracelets',
      image: 'https://mysite.com/product/fun-main.jpg',
      url: 'https://mysite.com/summer-fun'
    },
    {
      id: '4423434343',
      quantity: 2,
      item_price: 5,
      title: 'Summer Shorts',
      category: 'shorts',
      image: 'https://mysite.com/product/shorts-main.jpg',
      url: 'https://mysite.com/summer-shorts'
    }
  ]
})
```

### Subscribe

When a person applies to start a paid subscription for a product or service you offer.

| Name     | Type   | Required | Description                                                                                                 |
| -------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------- |
| currency | string | No       | The currency for the value specified                                                                        |
| value    | number | No       | A numeric value associated with this event. This could be a monetary value or a value in some other metric. |
| sourceId | string | No       | The unique identifier of the sign-up source.                                                                |

```javascript
edgetag('tag', 'Subscribe', {
  sourceId: 'Email',
  currency: 'USD',
  value: 49.99
})
```

### Search

When a search is made.

| Name     | Tyep                     | Required | Description                                                                                                 |
| -------- | ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------- |
| currency | string                   | No       | The currency for the value specified                                                                        |
| value    | number                   | No       | A numeric value associated with this event. This could be a monetary value or a value in some other metric. |
| contents | [Content\[\]](#contents) | No       | A list of products or items related to this event                                                           |
| search   | string                   | No       | A search query made by a user.                                                                              |

```javascript
edgetag('tag', 'Search', {
  currency: 'USD',
  value: 10.50,
  search: 'summer',
  contents: [{
    id: '123123123',
    quantity: 1,
    item_price: 10.50,
    title: 'Summer Fun',
    category: 'bracelets',
    image: 'https://mysite.com/product/fun-main.jpg',
    url: 'https://mysite.com/summer-fun'
  }]
})
```

### Lead

When a sign-up is completed.

| Name     | Type   | Required | Description                                                                                                 |
| -------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------- |
| currency | string | No       | The currency for the value specified                                                                        |
| value    | number | No       | A numeric value associated with this event. This could be a monetary value or a value in some other metric. |
| name     | string | No       | Title of the product/page                                                                                   |
| category | string | No       | Category of the Item.                                                                                       |

```javascript
edgetag('tag', 'Lead', {
  category: 'offers',
  name: 'Special Offer',
  currency: 'USD',
  value: 49.99
})
```

### Type Definitions

#### Content

| Name        | Type                      | Required | Description                                                                            |
| ----------- | ------------------------- | -------- | -------------------------------------------------------------------------------------- |
| id          | string                    | Yes      | Id of the Item. Like Product ID                                                        |
| quantity    | number                    | Yes      | Quantity of the Item.                                                                  |
| item\_price | number                    | Yes      | Final price per unit of the content/product.                                           |
| variantId   | string                    | No       | Variation Id of the Item. Required if variant id is used as content id for any channel |
| sku         | string                    | No       | SKU of the Item. Required if sku is used as content id for any channel                 |
| title       | string                    | No       | Title of the listed Item.                                                              |
| description | string                    | No       | Product description used for the item.                                                 |
| category    | string                    | No       | Category of the Item. Comma separated                                                  |
| brand       | string                    | No       | Brand of the Item.                                                                     |
| type        | product \| product\_group | No       | Type of the item.                                                                      |
| image       | string                    | No       | Image URL of this Item.                                                                |
| url         | string                    | No       | URL of this Item.                                                                      |

#### Discount

| Name  | Type               | Required | Description                                                 |
| ----- | ------------------ | -------- | ----------------------------------------------------------- |
| code  | string             | Yes      | Code that was applied for the discount                      |
| type  | FLAT \| PERCENTAGE | No       | What was the discount type: flat amount or percentage based |
| value | string             | No       | Value that was applied for that discount code               |

### Additional Parameters

You can use the parameters below with every event. You need to add them in the payload/data parameter.

| Name               | Type              | Description                                                                                                                                 |
| ------------------ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| eventId            | string            | By default we generate a unique id for every event. If you want to pass your own id, you can use this parameter.                            |
| skipIPAddress      | boolean           | You can use this flag if you are sending offline event where you do not know IP of the user.                                                |
| source             | online \| offline | Define source of the event. By default we send online.                                                                                      |
| skipTransformation | boolean           | If you pass this flag, we will not transform/normalize your data. You will need to take care of this as we will just pass through the data! |

```javascript
edgetag('tag', 'Lead', {
  category: 'offers',
  name: 'Special Offer',
  currency: 'USD',
  value: 49.99,
  source: 'offline',
  skipIPAddress: true
})
```


# Arhitecture


# Key Concepts


# Shopify

Setting up Shopify is quick and easy because we create an app for you. This app handles all the event coding, meaning you don’t need any coding knowledge to get started.

To get started, you will need:\
\- access to your DNS provider (where you host name servers)\
\- permission to install a paid app in Shopify

<figure><img src="/files/S9CZWyKEAGU1Fw76KgAF" alt=""><figcaption></figcaption></figure>

Need help during installation? Join us on our [community Slack](https://join.slack.com/t/blotout-shared/shared_invite/zt-nzwq4zpj-hOpfoZUs9Ar0n~fSxPBaSw).

{% content-ref url="/pages/BC6aYPEZML5geuNZYTZz" %}
[Installation](/installation/shopify/installation)
{% endcontent-ref %}


# Installation

Setting up Shopify is quick and easy because we create an app for you. This app handles all the event coding, meaning you don’t need any coding knowledge to get started.

### Prerequisites

To get started, you will need:\
\- access to your DNS provider (where you host name servers)\
\- permission to install the app in Shopify

### Video guide

{% embed url="<https://www.loom.com/share/73125e5a99734e90adc6bfeaecb4613b>" %}

### Installing the app

To install the app, visit <https://apps.shopify.com/edgetag> and click 'Install'. Next, you will need to accept charges (ensure you have permission to install a paid app). Enter the email that you would like to associate this installation with.&#x20;

<figure><img src="/files/st5e2zQqSk7IFtmx5y32" alt=""><figcaption><p>Login</p></figcaption></figure>

As you click Register/Login, we will create an account for you (if you don't already have one) and connect it with your Shopify store.

### Setting up DNS

Now that the account is created, we need to set up your domain.

If your store is not in production yet and the domain is not set up, you will need to enter it manually.

<figure><img src="/files/n2l4ZhofUrQAPf2xVg4L" alt=""><figcaption><p>Setting up domain</p></figcaption></figure>

If the store is already set up, we will automatically get the domain and set it up for you.

The domain is set, now we can create a first-party subdomain for you. This would ensure that signals will be more resilient. Each customer receives their own infrastructure and database, ensuring that data remains isolated, not mixed with other customers.

You will get two DNS records that you will need to set up. The first one is TXT, which ensures ownership of the domain that you are adding. The second one is CNAME, which handles traffic routing, ensuring that signals are sent to your infrastructure.&#x20;

{% hint style="info" %}
If you don't have access to the DNS provider where you host your domain, click on 'Email instructions'. This way, you can share these records with your IT department or DevOps team that has access.
{% endhint %}

Below, you can find links to popular providers on how to add records:

* [Amazon](https://documentation.unbounce.com/hc/en-us/articles/360059868872-Setting-Up-Your-CNAME-with-Amazon-Route-53-AWS-)
* [Cloudflare](https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-dns-records/)
* [GoDaddy](https://www.godaddy.com/help/add-a-cname-record-19236)
* [Google](https://support.google.com/a/answer/47283?hl=en\&sjid=105704025515093920-NC)
* [Namecheap](https://www.namecheap.com/support/knowledgebase/article.aspx/9646/2237/how-to-create-a-cname-record-for-your-domain/)
* [Shopify](https://help.shopify.com/en/manual/domains/managing-domains/edit-dns-settings)

<figure><img src="/files/3PKjnXmeaSoiX3LCoWNv" alt=""><figcaption><p>TXT Record</p></figcaption></figure>

<figure><img src="/files/Nq9ewNaiRVOfoG78GUMU" alt=""><figcaption><p>CNAME</p></figcaption></figure>

### Events

The final step in the installation process is to enable App Embed in your theme, which allows us to capture events in the store. To do this, click on the "Theme customization" link. This will open the theme settings. Next, locate the Blotout EdgeTag app and toggle it on. Once you’ve done that, click the "Save" button in the top right corner. After the save is successful, you can close the tab. Finally, click on "Finish."

<figure><img src="/files/8JCKLMetroaCUHAZZxVP" alt=""><figcaption><p>Enabling code</p></figcaption></figure>

You will see our dashboard, which includes a prompt to add your first channel.

<figure><img src="/files/IFUFfvhfSXfjlxgeBKEl" alt=""><figcaption><p>Dashboard</p></figcaption></figure>

### Next steps

Installation is now completed. The next step would be adding a channel.


# Adding a channel

As the next step, we would like to add your first channel. We support many different channels; for demo purposes, we will add Meta, as it is our most popular channel.

### Video guide

{% embed url="<https://www.loom.com/share/b93f3e61f0c647148b3962f6cf96481b>" %}

Adding a channel is really simple. You will need to select the channel in the Channels section. In our case, we will select Meta.

<figure><img src="/files/93HPGxys9gyXZuNSfOXm" alt=""><figcaption></figcaption></figure>

When filling out the channel form, please provide a name for the channel. We recommend choosing a name that clearly describes your connection. This will be useful in the future, especially if you have multiple connections for the same channel.

<figure><img src="/files/Rl92Nyguk9VWicSS3k7F" alt=""><figcaption></figcaption></figure>

We also offer the option to restrict the channel to a specific geographic location (this is optional). This allows you to create region-specific pixels with a straightforward configuration. If you have customers from both the US and the EU, you can separate them by creating two different pixels and targeting them accordingly.

Once you have entered the name, click on "Authenticate." This will redirect you to the Facebook authorization flow, where you will need to confirm your identity and grant the necessary permissions for our system to function correctly.

<figure><img src="/files/HpQ5XSz6FGJtM1Ysb0Ds" alt=""><figcaption><p>Facebook permissions</p></figcaption></figure>

After completing the Facebook process, you will be redirected back to our app to complete the channel setup.

Now we need to add the Dataset ID and advertiser ID. You can find detailed documentation in our [Meta onboarding link](/channels/meta/onboarding).

<figure><img src="/files/ZUc1aqApusEY7X0oFCVf" alt=""><figcaption></figcaption></figure>

After providing both IDs, click Update to successfully add your first channel.


# App overview

### Video Guide

{% embed url="<https://www.loom.com/share/6c31aff38c734ade9c2748a26335fe7f>" %}

### Channels

In the channel section, you can add a variety of channels that we support. Clicking on a connected channel will take you to an edit page where you can modify the specific settings for that channel. You also have the option to delete a channel from the edit page.

If you haven't added your first channel yet, check out our guide on how to do so.

{% content-ref url="/pages/X29CrxH4QKRgXQj10hSa" %}
[Adding a channel](/installation/shopify/adding-a-channel)
{% endcontent-ref %}

### Channel Errors

It's important to know when things go wrong. Channel errors can provide valuable insights into these issues. Errors are categorized by channel and then further divided into specific categories, offering a comprehensive view of what needs to be fixed. Each error also includes a payload viewer, which helps you better understand the nature of the error.

<figure><img src="/files/1SgnEwaGDofgfgze46zp" alt=""><figcaption><p>Detail view of the error</p></figcaption></figure>

### Events

In the events section, you can see how many events have been processed.

<figure><img src="/files/Lgfo6IljiiEW88TrpeGu" alt=""><figcaption></figcaption></figure>

### Cookie Banner

As laws and privacy regulations evolve, cookie banners are becoming increasingly important. We provide automatic support for well-known providers, making implementation straightforward. Simply select your preferred provider, and you’ll be all set.

* [CookieBot](https://www.cookiebot.com/)
* [Ethyca](https://www.ethyca.com/)
* [OneTrust](https://www.onetrust.com/)
* [Shopify](https://shopify.dev/docs/api/customer-privacy)
* [Termly](https://termly.io/)
* [UserCentrics](https://usercentrics.com/)
* [Transcend](https://transcend.io/platform/consent-management)
* [TrueVault](https://www.truevault.com/)

{% hint style="info" %}
If your cookie banner provider supports the Shopify Privacy API, select **Shopify** as the provider.
{% endhint %}

If we do not support your cookie banner implementation, you have two options to choose from. You can connect your solution to the [Shopify Privacy API](https://shopify.dev/docs/api/customer-privacy) and select Shopify as your cookie provider. Alternatively, you can create a direct implementation with EdgeTag using our [consent function](/implementation/overview).

### Coupon Code

If you received a coupon code from the Blotout team, you can enter it in this box. The billing page in Shopify will open, where you will need to confirm the change.

### Custom Code

Sometimes you want to do more and capture additional events. That is not a problem, we support an easy way to add your additional JavaScript code.

#### Newsletter Selectors

Our app automatically captures many newsletter popups. If you have additional ones, please provide the ID or Class value of that form. If you have more than one, separate them with a comma. Once the form is submitted, we will collect user information.

<figure><img src="/files/Q5kLvJqvjgW2Hr080dDI" alt=""><figcaption></figcaption></figure>

#### Theme code

You want to do more? Not a problem. You can write complete JavaScript code that sends additional events, captures form submissions, or retrieves user information. Read more about the available functions in the [Browser Implementation Guide](/implementation/browser).

<figure><img src="/files/VcyTzHf5sF9ZyYI4blG2" alt=""><figcaption></figcaption></figure>

### Support

If you are not very familiar with the code or want to ensure that your EdgeTag implementation is performing at its best, we offer a support plan. Once you subscribe to the plan, we will reach out to you to have a detailed session where we will first discuss your requirements. After that, our solution engineers will evaluate your shop and write any additional code necessary to capture all funnels effectively.

Need more help? Join us on our [community Slack](https://join.slack.com/t/blotout-shared/shared_invite/zt-nzwq4zpj-hOpfoZUs9Ar0n~fSxPBaSw).


# Consent

For Shopify, we provide support for multiple consent providers right out of the box. This means that when you select a consent provider, we will automatically connect it to our consent API, ensuring that the consent string is saved and utilized properly.\
\
To set up consent, simply go to our Shopify App and click on "Settings" within the app. In the Cookie Banner section, select the consent provider you are using. The Shopify option refers to the [Customer Privacy API](https://shopify.dev/docs/api/customer-privacy) integration. Many consent providers are natively integrated with the Privacy API, so if your provider supports it, you can select SHOPIFY for the consent banner option.

<figure><img src="/files/e418d0UHSQ5UFJLjipNv" alt=""><figcaption></figcaption></figure>

You can also read more about our consent function if you are implementing your own cookie banner or if you have a provider that we don't support.

{% content-ref url="/pages/DthjFEpwcxdXuxGPVP4q" %}
[Consent](/implementation/browser/consent)
{% endcontent-ref %}


# Shopify

Our Shopify cookie banner implementation uses the [Customer Privacy API](https://shopify.dev/docs/api/customer-privacy) and connects it to our [consent function.](/implementation/browser/consent#examples)

Let's go over how we are doing this and what to expect.

Our consent handler is only triggered once EdgeTag is fully initialized, for which we are listening for `edgetag-initialized` event that EdgeTag SDK fires once we get data back from the edge.

Next, we will determine whether the user is new or existing. For new users, we will read the current settings in the customer privacy section and send them to our consent function. For existing users, we already have their consent stored in memory.

For all users, we will listen for the consent change event, which will be triggered by Shopify via the [`visitorConsentCollected`    event](https://shopify.dev/docs/api/customer-privacy#use-an-event-listener). Once we receive a signal from Shopify that the customer changed their consent, we will process the data and send it to our consent function.

Let's examine how we process settings before sending them to the consent function. In our processing, we are looking for two different scenarios. Is the user in a region where the sale of data is applied or not? To check that we are calling [`saleOfDataRegion` function](https://shopify.dev/docs/api/customer-privacy#check-if-data-sale-opt-out-is-available).  If the user is in that region, we will use `saleOfData` setting when setting consent for marketing and analytics.

If the user is not in that region, we will map `analyticsAllowed` to our analytics consent and `marketingAllowed` to our marketing consent.

You can also see our pseudo-code below to explain what we are doing:

```javascript
let marketing = false
let analytics = false

if (window.Shopify.customerPrivacy.saleOfDataRegion()) {
 marketing = window.Shopify.customerPrivacy.saleOfDataAllowed()
 analytics = window.Shopify.customerPrivacy.saleOfDataAllowed()
} else {
  marketing = window.Shopify.customerPrivacy.marketingAllowed()
  analytics = window.Shopify.customerPrivacy.analyticsProcessingAllowed()
}

edgetag(
  'consent', 
  null, 
  { all: false, necessary: true, advertising: marketing, analytics: analytics }
)
```

If our logic doesn't match your consent banner, you can always select CUSTOM in our Cookie banner selection and use our [consent function](/implementation/browser/consent#examples) to send us consent.


# Wordpress

Setting up WordPress is quick and easy, as we provide a dedicated app for you. This app manages the pageView event, and if you have WooCommerce installed, it automatically captures all e-commerce events. If you want to track more events, please refer to [our guide](/implementation/browser/events) on how to send additional events.

To begin, you must have an EdgeTag account and a website added to it. If you haven't added your website yet, please check out the [Getting Started guide](/overview/getting-started).

<figure><img src="/files/uUe8CTi7KpQCn3mcTk11" alt=""><figcaption></figcaption></figure>

Need help during installation? Join us on our [community Slack](https://join.slack.com/t/blotout-shared/shared_invite/zt-nzwq4zpj-hOpfoZUs9Ar0n~fSxPBaSw).

{% content-ref url="/pages/mJHm5SAe1yyXM2dy0K7y" %}
[Installation](/installation/wordpress/installation)
{% endcontent-ref %}


# Installation

Setting up WordPress is quick and easy, as we provide a dedicated app for you.

Our plugin can be found on the official site: <https://wordpress.org/plugins/blotout-edgetag>

### Adding plugin

You can find our plugin in the plugin store. Log in to your WordPress instance and click on 'Plugins' in the left menu. In the search box, enter 'edgetag'. Once you see our plugin, click 'Install Now.'

<figure><img src="/files/yQXZXhlbbcHrukViqXh5" alt=""><figcaption></figcaption></figure>

After installation is completed, click on Activate.

<figure><img src="/files/emE8aIFtFpPZwSSNeGVX" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
After activating the plugin, we recommend enabling automatic updates to ensure you always have the latest version.
{% endhint %}

In the left menu, you will now see the EdgeTag plugin.

### Configuration

Configuring our plugin is really simple. You will need to access the EdgeTag app and copy the domain (see the example below). If you don't have your domain yet, please follow our [Onboarding Guide](https://docs.edgetag.io/overview/getting-started).

<figure><img src="/files/J1kvaLoZwfJaIFl1gcXr" alt=""><figcaption></figcaption></figure>

After acquiring your domain, navigate to our plugin in WordPress and paste the domain into the EdgeTag URL field. Then, click Save.

<figure><img src="/files/NFP3LJhY0dHJdMsVyIrr" alt=""><figcaption></figcaption></figure>

Congratulations, your installation is completed.


# App overview

<figure><img src="/files/RW1LEe2m3RnH5vY1HPq2" alt=""><figcaption></figcaption></figure>

### Newsletter selectors

Our plugin automatically attempts to capture as many email inputs from the site as possible. However, we may not detect all of them, especially when custom lead forms are used. In such cases, you can use this field to add HTML selectors for those forms. You can input multiple selectors here, separated by commas.

Example: `#my-form, .lead-gen-forms`

### Header Script

When adding additional events to be sent to EdgeTag, you can use this field to write code for capturing more events. Make sure to wrap this code in a script tag. For guidance on sending browser events, please refer to our [guide](/implementation/browser).

{% code title="Example of lead form" %}

```html
<script>
  document.getElementById('leadForm').addEventListener('submit', function(e) {
    const form = e.target;
    const formData = new FormData(form);

    edgetag('data', {
      firstName: formData.get('firstName'),
      lastName: formData.get('lastName'),
      email: formData.get('email'),
      phone: formData.get('phone'),
    });
    
    edgetag('tag', 'Lead');
  });
</script>
```

{% endcode %}


# WooCommerce

Setting up WooCommerce is quick and easy, as we provide a dedicated plugin to help you get started. Our WordPress plugin handles WooCommerce automatically, so please refer to our WordPress installation guide on how to get started.

{% content-ref url="/pages/sYNPPgsIWY4GqgWQi9f3" %}
[Wordpress](/installation/wordpress)
{% endcontent-ref %}

### Automatic events

We capture most of our standard events out of the box for you. Events that we capture for you automatically:

* PageView
* ViewContent
* AddToCart
* InitiateCheckout
* AddShippingInfo
* Purchase

### Custom events

If you would like to capture any other events, please refer to our guide for capturing events in the browser.

{% content-ref url="/pages/02EmLjmzMffkfovNsTXP" %}
[Events](/implementation/browser/events)
{% endcontent-ref %}


# BigCommerce

Setting up BigCommerce is quick and easy, as we provide a dedicated snippet to help you get started.&#x20;

To begin, you must have an EdgeTag account and a website added to it. If you haven't added your website yet, please check out the [Getting Started guide](/overview/getting-started).


# Installation

### Download template

For the codification step, you can use our BigCommerce template and configure the selectors according to your site.

<a href="https://app.edgetag.io/assets/script-templates/big-commerce/big-commerce.txt" class="button primary" data-icon="down-to-bracket">Download template</a>

{% hint style="warning" %}
BigCommerce templates work for most sites. If you have a custom theme/plugin, please make sure to change the code according to the website.
{% endhint %}

### Adding a template

Now that we have the code ready, let's add it to your BigCommerce.

Select "Storefront" in the BigCommerce dashboard.

<figure><img src="/files/727Y5D12iENvwhPNfrPy" alt=""><figcaption></figcaption></figure>

Select “Script Manager” and Click on “Create a Script”

<figure><img src="/files/uIeo16mGidHqYsMfhV31" alt=""><figcaption></figcaption></figure>

We will now need to configure the script.

* Change “location on page” to “Head”; this will ensure that the script will be loaded inside the head tag.
* Change “Select pages where script will be added” to “All pages”; this will ensure that the script will be loaded on all pages
* Change “Script category” to “Analytics”
* Change “Script type” to “Script”
* Paste our template in "Script contents"

<figure><img src="/files/fQ4wXq5OEIniIjrXbeEK" alt=""><figcaption></figcaption></figure>

Finally, click on the Save button at the bottom.


# FAQ

### What are the best practices for event creation?

Here are some worth remembering;

1. Always code your upper funnel events along with the purchase event
2. Map every single user's data:
   1. Contact footers
   2. Subscriptions
   3. Purchases
   4. etc.

### What if I have a custom application or theme?

You’ll need to adjust/modify the code snippets according to the theme you have installed.&#x20;

Reach out to our [shared slack](https://join.slack.com/t/blotout-shared/shared_invite/zt-nzwq4zpj-hOpfoZUs9Ar0n~fSxPBaSw) if you need help with this.

### Will Blotout be building a plug-in or BigCommerce app?

Yes, we are planning to build a BigCommerce app.


# Salesforce CC

Setting up Salesforce CC is quick and easy, as we provide a dedicated app for you.&#x20;

To begin, you must have an EdgeTag account and a website added to it. If you haven't added your website yet, please check out the [Getting Started guide](/overview/getting-started).

{% content-ref url="/pages/W1GfmlcQwtqm6nbiy2iU" %}
[Installation](/installation/salesforce-cc/installation)
{% endcontent-ref %}


# Installation

Setting up Salesforce CC is quick and easy, as we provide a dedicated app for you.

To obtain the app, please get in touch with our support team at <support@blotout.io>.

Once you get the zip file, you can start the onboarding.

### Metadata

To add Blotout metadata, go to Administration > Site Import & Export in Salesforce.

<figure><img src="/files/UlrH4kftxHpWtCT3nrlL" alt=""><figcaption></figcaption></figure>

Upload the metadata\_blotout.zip file that you got from our team.

<figure><img src="/files/H1Xyz1KdtHaeaIecr1Sl" alt=""><figcaption></figcaption></figure>

Import "instance/metadata\_blotout.zip". It can take a while for the import to complete.

<figure><img src="/files/O5LqMOP0OXOZjImzPrKW" alt=""><figcaption></figcaption></figure>

### Cartridge

Now that we have metadata, we need to add a Cartridge. Unzip the int\_blotout.zip file and the int\_blotout folder into the codebase as siblings to your other cartridge folders.

To add a Cartridge, navigate to Administration> Site Development > Manage Sites.

<figure><img src="/files/U5vXknFZ7cosUHfBZwoW" alt=""><figcaption></figcaption></figure>

Select your site and go to Settings.

<figure><img src="/files/WQ80QowfZF8Q31Ct3Kmh" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/19jRHakuYp8B5XNi7nLO" alt=""><figcaption></figcaption></figure>

Add “int\_blotout” at the start of the cartridge path input labeled "Cartridges".

<figure><img src="/files/obY4UMY8iqjVVLhUiHoS" alt=""><figcaption></figcaption></figure>

### Configuration

The last step is to add your EdgeTag URL that you created in your onboarding to Salesforce. This way, you will connect Salesforce and EdgeTag.

Select your site.

<figure><img src="/files/C4QM45lIan8aAwUdiu0H" alt=""><figcaption></figcaption></figure>

Navigate to Merchant Tools > Site Preferences > Custom Preferences

<figure><img src="/files/gk6rh2I5pck9WHIrmGpU" alt=""><figcaption></figcaption></figure>

Search for “blotout” and select Blotout preferences. Select an appropriate instance type and enter the EdgeTag URL that was generated for you as part of onboarding.


# Magento


# Overview

When we started with EdgeTag, we had a clear vision: to create a solution that prioritizes privacy while ensuring top-notch security. Our goal was to differentiate ourselves from typical SaaS tools by providing an infrastructure accessible to everyone—marketers, developers, and large organizations with multi-team structures.

Each of our customers receives their own isolated environment, which includes a dedicated database, processing capabilities, endpoints, and storage. At no point is any data mixed between customers.

We offer two hosting options for our solution: **managed** and **self-hosted**.

### Which one to pick?

{% hint style="info" %}
Both hosting options provide the same features of EdgeTag.
{% endhint %}

The primary consideration is whether you already have a Cloudflare account to deploy the infrastructure, or if you prefer to store data in your own environment and have direct access to it.

{% content-ref url="/pages/VyppyWMxHHCDXZLeHIyy" %}
[Managed](/hosting/managed)
{% endcontent-ref %}

{% content-ref url="/pages/6xnslANUnYY7zliMOvot" %}
[Self-hosting](/hosting/self-hosting)
{% endcontent-ref %}

{% hint style="warning" %}
Please ensure that you fully understand these options before selecting a hosting option, as we will not be able to migrate from managed to self-hosted, or vice versa.
{% endhint %}


# Managed

Managed hosting is a popular choice because it eliminates the need for you to manage anything yourself. You'll have your isolated environment with no worry about how it works.&#x20;

When you sign up, simply provide your domain, and we will set up your entire infrastructure in our Cloudflare account.


# Self-hosting

Choosing the self-hosting option requires some effort, but it provides you with direct access to your infrastructure, and your data will be stored in your own account.

We utilize Cloudflare and its products for hosting services. We continually strive to stay current with our offerings, providing you with the best solutions possible.

### Cloudflare services

Below you can see the services that we are using from the Cloudflare ecosystem:

* [Analytics Engine](https://developers.cloudflare.com/analytics/analytics-engine/) (analytics)
* [Custom hostnames ](https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/domain-support/)(domain)
* [D1](https://developers.cloudflare.com/d1/) (database)
* [KV](https://developers.cloudflare.com/kv/) (key-value storage)
* [Queues](https://developers.cloudflare.com/queues/)
* [R2](https://developers.cloudflare.com/r2/) (file storage)
* [Workers](https://developers.cloudflare.com/workers/) (processing)
* [Workers for Platforms](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/) (OEM)
* [Workflows](https://developers.cloudflare.com/workflows/)

### Onboarding

To get started with self-hosting, follow these simple steps.&#x20;

First, we will check if your current Cloudflare account (or the new one you created) is set up correctly.&#x20;

Next, you'll need to generate a token that you will paste into the host form in the EdgeTag app.&#x20;

{% hint style="warning" %}
If you are setting up hosting as an OEM or are not using Cloudflare for name servers (NS), you will also need to enable [custom domain support](/hosting/self-hosting/custom-domains) to generate first-party subdomains.
{% endhint %}

Let's first check if the account is set up correctly.


# Account Setup

### Video Guide

{% embed url="<https://www.loom.com/share/276fb60b12ad448ab08796425751f72a>" %}

### Billing

Once you create an account with Cloudflare, the first step is to add your credit card. In the left menu, click on Manage Account -> Billing -> Payment. Enter your billing information and credit card details.

<figure><img src="/files/bvIa1zmzCaCO12UhWPMQ" alt=""><figcaption></figcaption></figure>

### Worker

Now that we have the billing set up, let's enable Workers. On the left menu, click "Compute (Workers)". You will see a welcome screen from which we will select the last option and then click Deploy (this Hello World worker can be deleted after setup is done). After that, click on Workers & Pages on the left menu.

<figure><img src="/files/8MOSF6B2mjGNK5Xmcprr" alt=""><figcaption></figcaption></figure>

### Plan

Now that workers are enabled, we need to switch from the Free plan to the Paid plan. The Cloudflare Paid plan is quite generous, offering a lot for just $5 per month. To upgrade, click "Upgrade plan" in the top-right corner.&#x20;

<figure><img src="/files/VzSfzzZDdLzUALkyVgnk" alt=""><figcaption></figcaption></figure>

Select "Purchase Workers Paid" and click on "Purchase." Once the purchase is complete, you will see a "Paid" label in the top right corner of the Workers page.

With the switch to the Paid plan, you have enabled almost all the features we need; we only have R2 left.

R2

To enable R2, go to "R2 Object Storage" -> "Overview" in the left menu. Now you just need to add R2 to your subscription.

Congratulations, your Cloudflare is now ready to connect.


# Access information

To create or update your infrastructure and ensure everything is running correctly, we require your Cloudflare Account ID and API token.

### Video Guide

{% embed url="<https://www.loom.com/share/b5b70b9249df4e01a20474b55d8c20ae>" %}

### Account ID

Click on "Workers & Pages" in the left menu, and you will find the Account ID on the left side.

<figure><img src="/files/hQ7EBxfwd4Aw3vSOXMAn" alt=""><figcaption></figcaption></figure>

### API Token

To generate an API token, you will need to go to "Manage Account" -> "Account API tokens" in the left menu and click "Create Token".

Select the "Edit Cloudflare Workers" template to speed up the process. &#x20;

<figure><img src="/files/ATXgO9iBJYuKtm4xJp7u" alt=""><figcaption></figcaption></figure>

We already have a comprehensive list of permissions that are required for Workers to perform their duties. Now we just need to add 3 more:

* Account -> Account Analytics (Read)
* Account -> D1 (Edit)
* Zone -> DNS (Edit)

<figure><img src="/files/Iv4TlnHH18nqMJVy0xyt" alt=""><figcaption></figcaption></figure>

The last thing that we need to do is define Zone Resources. Make sure that you select the Specific zone and then your zone.

<figure><img src="/files/bhvhLCPQg7B6ZGNkz9Px" alt=""><figcaption></figcaption></figure>

If you have multiple zones and encounter an error in EdgeTag stating that your token should only be associated with one zone, select "All Zones from an Account," click "Add More," then select "Specific Zone" and choose the zone that is not used for EdgeTag. Repeat this process, excluding zones, until only the one used by EdgeTag remains.

<figure><img src="/files/eIg75pDvWIVjT5Ql0oFW" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
If your domain is not hosted in Cloudflare, ensure that you add support for Custom domains on the next page.
{% endhint %}


# Custom domains

If your domain's DNS records are not hosted on Cloudflare or you are an OEM partner hosting multiple customers (domains), you will need to add support for Custom domains.&#x20;

{% hint style="warning" %}
You will still need to add one domain that will be an anchor for custom domains. You can purchase it directly from Cloudflare or transfer an existing one.
{% endhint %}

### Video guide

{% embed url="<https://www.loom.com/edit/dcd5b3a300bd43f9bf7a49c6827a096a>" %}

### Custom Hostname

First, we will need to enable Custom Hostnames. Click on "Account home" and select the domain that you will use for EdgeTag. In the left menu select "SSL/TLS" -> "Custom Hostnames". You will need to enable it (100 domains is free).

<figure><img src="/files/YBSUObRhew4TxzDAzF39" alt=""><figcaption></figcaption></figure>

After it is enabled, you need to add a Fallback Origin. Normally, you would set it up as fallback.mysite.com or customers.mysite.com (mysite.com is your domain in Cloudflare).

<figure><img src="/files/UXTmu3ynCphqbmMX0DyO" alt=""><figcaption></figcaption></figure>

To connect to the newly created Fallback Origin, click on DNS in the left menu and add the following record:

Type: `AAAA`\
Name: `customers` (or the name that you used in the Fallback Origin)\
Content: `100::`

<figure><img src="/files/5MrYVgUvqzlKUd0YZPtL" alt=""><figcaption></figcaption></figure>

### API Token

The last step in Cloudflare is to update API Token permissions. Go to "Manage Account" -> "Account API tokens" and click on edit of your EdgeTag token.

<figure><img src="/files/XSuL8rvom0FKmKllR8bG" alt=""><figcaption></figcaption></figure>

We will need to add two more permissions:

* Zone -> Zone (read)
* Zone -> SSL and Certificates (edit)

<figure><img src="/files/7CNas6oM3iXhp7EYfTMj" alt=""><figcaption></figcaption></figure>

### Host configuration

Now, we will need to return to EdgeTag and check the Custom Hostname option for the Host.

<figure><img src="/files/pggwhc0ho9ALxLGPy89b" alt=""><figcaption></figcaption></figure>


# FAQ

### I have multiple zones in the Cloudflare account, and I'm getting an error

If you see a similar error in our EdgeTag app, please follow these steps:

<figure><img src="/files/cAdsC6QkNbKn2LzVkGV1" alt=""><figcaption></figcaption></figure>

1. Navigate to the token you created in Cloudflare and click "Edit."
2. Go to "Zone Resources" and ensure that you have selected only one zone, as shown below.

<figure><img src="/files/nhoGJv4CJpdmVB4Pt3zA" alt=""><figcaption></figcaption></figure>

If you haven’t selected only one zone, please modify your selection, save the token, and try again. If the issue persists, you will need to select all zones in your account and then exclude the ones you don’t need.

In my example, I have three zones: `edge-traffic.com` , `trustops.io` and `eddn.io` .

If I want to use `edge-traffic.com` I need to exclude `trustops.io` and `eddn.io` .

<figure><img src="/files/zzt8GGeK8yxd9l7XMUi4" alt=""><figcaption></figcaption></figure>

After you have updated your token configuration, save the changes and try again.


# Codify your headless Shopify store

### Who Should Follow This Guide:

This guide is intended for developers and technical teams managing headless Shopify stores who want to implement accurate event tracking using Blotout. If your Shopify store uses a custom frontend (headless) or custom checkout, you must follow these instructions to ensure proper event tracking.

### Why Event Codification is Required

When a Shopify store is headless, the Blotout Shopify App cannot automatically track events from the website. Shopify’s standard web pixel and app-based tracking only work with traditional (non-headless) stores.\
**Result:** Custom event codification is required to ensure accurate tracking of user interactions and conversions.

### Event Codification Requirements

The events you need to implement depend on **whether your storefront or checkout is headless**.

#### Case 1: Headless Storefront with Shopify Checkout

If your **pre-checkout experience is headless** (custom frontend) and the **checkout is hosted by Shopify**, you must codify the following events on your website:

<table><thead><tr><th width="198.33984375">Event Name</th><th>Trigger</th></tr></thead><tbody><tr><td><strong>PageView</strong></td><td>Fired on every page load</td></tr><tr><td><strong>ViewContent</strong></td><td>Fired on product detail pages with product payload</td></tr><tr><td><strong>AddToCart</strong></td><td>Fired when a product is added to the cart</td></tr><tr><td><strong>CompleteRegistration</strong></td><td>Fired when a user completes account registration | optional event</td></tr><tr><td><strong>Lead</strong></td><td>Fires on submission of the footer or pop-up subscribe form</td></tr></tbody></table>

**Note:** These events must be triggered from your headless frontend and sent to Blotout with the appropriate payload.

#### Case 2: Fully Headless Checkout (Custom Checkout)

If **both your storefront and checkout are headless**, you must additionally codify checkout and purchase-related events.

<table><thead><tr><th width="178.87890625">Event Name</th><th>Trigger</th></tr></thead><tbody><tr><td><strong>InitiateCheckout</strong></td><td>Fired when the user lands on the checkout page</td></tr><tr><td><strong>AddShippingInfo</strong></td><td>Fired when shipping details are submitted</td></tr><tr><td><strong>AddPaymentInfo</strong></td><td>Fired when payment details are submitted</td></tr><tr><td><strong>Purchase</strong></td><td>Fired once the purchase is successfully completed</td></tr></tbody></table>

### SDK Initialization and Event Dispatching

Before sending any events or user data, you must **initialize the Edgetag SDK**.

Once initialized, use the **`tag` method** to:

* Send event (e.g., PageView, AddToCart, Purchase)

Use **data/user method** to:

* Send user information and attributes

#### Basic Flow

1. Initialize the Edgetag SDK
2. Call the `tag` method to send events
3. Send user data or PII separately using data/user methods

### Sending User Information

All user information must be sent using the methods below only.

To send user information, please follow the relevant integration guide based on your implementation method:

1. [**Native JavaScript**](https://docs.edgetag.io/implementation/browser/user-info)
2. [**NPM Package**](https://docs.edgetag.io/implementation/headless/user-info)
3. [**HTTP API**](https://docs.edgetag.io/implementation/http/data)

**Important:** Always send the [standard keys](https://docs.edgetag.io/implementation/browser/user-info/standard-keys) with the proper type; otherwise, the events will fail validation.

### Methods for Event Codification

There are multiple supported methods to implement event tracking:

1. [**Native JavaScript**](https://docs.edgetag.io/implementation/browser)
   * Directly trigger events using custom JavaScript in your frontend.
2. [**NPM Packages**](https://docs.edgetag.io/implementation/headless)
   * Use our official or supported NPM packages to simplify integration.
3. [**HTTP API Calls**](https://docs.edgetag.io/implementation/http)
   * Send events directly to our ingestion endpoint from your backend or frontend (recommended server-side).
4. [**Webhooks**](https://docs.edgetag.io/channels/webhook)

### Event Payload Requirements

Each event **must include the required payload**, such as:

* Product or cart data
* Order values and currency
* Event-specific metadata

Please follow the [**standard event payload**](https://docs.edgetag.io/overview/standard-events) **specification** outlined in our official integration guide to ensure proper processing and activation.


# Monetize Klaviyo

### Before we Monetize&#x20;

Before you get started on how-to monetize the incremental audiences, we recommend that you check if you have Klaviyo and Blotout EdgeTag setup correctly.

{% hint style="info" %}
Note: Please ensure you have added Klaviyo and completed the authorization
{% endhint %}

<table data-header-hidden><thead><tr><th width="216.6796875"></th><th></th><th></th></tr></thead><tbody><tr><td><strong>Blotout Event</strong></td><td><strong>Equivalent  Klaviyo Event</strong></td><td><strong>Description</strong></td></tr><tr><td>Blotout_PageView</td><td>Active on Site</td><td>Top of the funnel - every page</td></tr><tr><td>Blotout_ViewContent</td><td>Viewed Product</td><td><p>PDP - Product Page viewed</p><p>One per page</p></td></tr><tr><td>Blotout_AddToCart</td><td>Added to Cart</td><td><p>Added to Cart event.</p><p>One per Cart addition</p></td></tr><tr><td>Blotout_InitiateCheckout</td><td>Checkout Started</td><td>Started Checkout</td></tr><tr><td>Blotout_Purchase</td><td>Placed Order</td><td>Site Purchases only</td></tr></tbody></table>

{% hint style="info" %}
For a list of standard [Blotout EdgeTag Events please refer to our standard event catalog](/overview/standard-events)
{% endhint %}

If these events are not showing up, please reach out to your Blotout EdgeTag contact directly or via our Slack Forum.

### Calculating Incremental Audience

Let's check a couple of segments to measure incremental audiences for lifecycle visitors.

The **two we recommend** are PDP page visits and Added to Cart for;

1. Viewed Product incremental audience
2. Added to Cart incremental audience

These give you a very good idea on how many incremental members you are able to re-identify. You can do the math by checking revenue per recipient to understand overall incremental revenue.

Lets start with Viewed Product.

#### Incremental Viewed Product

We recommend you use L3 days as a measuring stick to measure incremental audience that your Blotout EdgeTag Server captures for you.

* Start with logging in to Klaviyo
* Go to Segments and Lists
* Create Segment
* Follow steps below

**Step-by-Step**

<figure><img src="/files/qBHUpU5ivO5XI6tPMueI" alt=""><figcaption></figcaption></figure>

Above is an example for comparing Blotout Events with Regular Klaviyo events; here are the rules -

**Segment Name:** Test VP (Klaviyo) L3

**Rule 1:** Where someone has done Viewed Product at least once in last 3 days

**COMPARE TO (TEST INCREMENTAL)**

**Segment Name:** Test VP (Klaviyo + Blotout) L3

**Rule 1:** Where someone has done Viewed Product at least once in last 3 days

OR

**Rule 2:** Where someone has done Blotout\_ViewContent at least once in last 3 days

***

<figure><img src="/files/VH8lbBNDd1l1Q18wwAYe" alt=""><figcaption></figcaption></figure>

The above example compares Blotout with Elevar SS plus Klaviyo events.

**Segment Name:** Test VP (Klaviyo + Elevar) L3

**Rule 1:** Where someone has done Viewed Product at least once in last 3 days

OR

**Rule 2:** Where someone has done Viewed Product Elevar SS at least once in last 3 days

**COMPARE TO (TEST INCREMENTAL)**

**Segment Name:** Test VP (Klaviyo + Elevar + Blotout) L3

**Rule 1:** Where someone has done Viewed Product at least once in last 3 days

OR

**Rule 2:** Where someone has done Viewed Product Elevar SS at least once in last 3 days

OR

**Rule 3:** Where someone has done Blotout\_ViewContent at least once in last 3 days

**Pro-tip:** Just because everyone says they are server-side, does not mean managing ID assets are very well understood. The upside on mid-funnel here is the same advantage on all growth channels

#### Incremental Added to Cart

We recommend you use L3 days as a measuring stick to measure the incremental audience that your Blotout Server captures for you.

* Start with logging in to Klaviyo
* Go to Segments and Lists
* Create Segment
* Follow steps below

**Step-by-Step:**

<figure><img src="/files/3Pf6NYQzI0a44WdGLOlV" alt=""><figcaption></figcaption></figure>

Refer the above image as an example to comparing Blotout with Regular Klaviyo events; here are the rules -

**Segment Name:** Test ATC (Klaviyo) L3

**Rule 1:** Where someone has done Added to Cart at least once in last 3 days

**COMPARE TO (TEST INCREMENTAL)**

**Segment Name:** Test ATC (Klaviyo + Blotout) L3

**Rule 1:** Where someone has done Added to Cart at least once in last 3 days

OR

**Rule 2:** Where someone has done Blotout\_AddToCart at least once in last 3 days

***

<figure><img src="/files/pJdEPrFzVk8kDT5k60da" alt=""><figcaption></figcaption></figure>

Above image is Comparing Blotout with Elevar SS plus Klaviyo events.

**Segment Name:** Test ATC (Klaviyo + Elevar) L3

**Rule 1:** Where someone has done Added to Cart at least once in last 3 days

OR

**Rule 2:** Where someone has done Added to Cart Elevar SS at least once in last 3 days

**COMPARE TO (TEST INCREMENTAL)**

**Segment Name:** Test ATC (Klaviyo + Elevar + Blotout) L3

Rule 1: Where someone has done Added to Cart at least once in last 3 days

OR

Rule 2: Where someone has done Added to Cart Elevar SS at least once in last 3 days

OR

Rule 3: Where someone has done Blotout\_AddToCart at least once in last 3 days

Pro-tip: Just because everyone says they are server-side, does not mean managing ID assets are very well understood. The upside on mid-funnel here is the same advantage on all growth channels

### Monetization

Pro-Tip: You can estimate your daily upside by purely measuring revenue per email from your existing flows

There are **two key** variables in building incremental flows;

* Variable-1: Get your flow triggers right
* Variable-2: Get your email templates right

{% hint style="info" %}
Note: Blotout cannot help with email or SMS templates but your agency can help you. You can always reach us out on our Shared Slack
{% endhint %}

Here are the key templates for each of the flows.

#### Abandoned Browse Flow

1. Clone your existing Abandoned Browse flow
2. Select "Blotout\_ViewContent" as Event
3. Replace (Clone) -> (Blotout)
4. Start editing the flow
5. Click on Flow Trigger
6. Add Rule -> AND someone has done something zero times (select Viewed Product) since starting the flow
7. Add Rule -> AND someone has done something zero times (select Viewed Product) 1 hour before the flow triggered&#x20;
8. If someone has Blotout\_AddToCart zero times, since starting this flow
9. If someone has Blotout\_InitiateCheckout zero times, since starting the flow.
10. If someone has Received Email zero times in the last 7 days where Flow equals to {the name of the flow} (flow through which the Blotout flow is cloned). This step is only needed when both flows are live.

**Goal**: Goal here is to ensure that user has not seen the Viewed Product Event

#### Abandoned Cart Flow

1. Clone your existing Abandoned Cart flow
2. Select "Blotout\_AddToCart" as Event
3. Replace (Clone) -> (Blotout )
4. Start editing the flow
5. Click on Flow Trigger
6. Add Rule -> AND someone has done something zero times (select Added to Cart) since starting the flow
7. Add Rule -> AND someone has done something zero times (select Added to Cart) 1 hour before the flow triggered
8. If someone has Blotout\_InitiateCheckout zero times, since starting the flow.
9. If someone has Received Email zero times in the last 7 days where Flow equals to {the name of the flow} (flow through which the Blotout flow is cloned). This step is only needed when both flows are live.

**Goal**: Goal here is to ensure that user has not seen the Added to Cart Event

#### Abandoned Checkout Flow

1. Clone your existing Abandoned Checkout flow
2. Select "Blotout\_InitiateCheckout" as Event
3. Replace (Clone) -> (Blotout )
4. Start editing the flow
5. Click on Flow Trigger
6. Add Rule -> AND someone has done Checkout Started zero times since starting this flow
7. If someone has Received Email zero times in the last 7 days where Flow equals to {the name of the flow} (flow through which the Blotout flow is cloned). This step is only needed when both flows are live.

**Goal**: Goal here is to ensure that user has not seen the Initiate Checkout Event

Great! May the force of retention be with you.

{% hint style="info" %}
Pro-Tip: For high LTV brands where retention is high, we recommend building a Site Revisit flow as well
{% endhint %}


# Verify Event Transforms

### What are keyword conversions?

Keyword conversions is a feature where you can **map keywords** to custom conversions. In a Purchase event, if we see any mapped keyword present in the fields of the event payload (see below) after we transform them, we fire the mapped custom conversion. E.g. if keyword "shirt" is present in `contents.title`, we fire the mapped custom conversion of that keyword.

### Transform payload fields

When looking for keywords, we are considering the following fields in the payload:

* `contents.title`
* `contents.brand`
* `contents.description`
* `keywords`

When transforming, we will remove punctuations, replace word split characters with spaces, changes text to lower case etc.&#x20;

**Examples**&#x20;

"White\_shirt" would be "whiteshirt"&#x20;

"Red\_Cap.Embroidered" would be "redcap embroidered"

### How to create a keyword conversion mapping?

Once you know the keyword that can be used, please go to the plugins page, select the plugin that you want to add keyword conversion mapping, add keyword in the left dropdown and the custom conversion name in the right dropdown and save the form.

<figure><img src="/files/KeZnxcsUvw9YR3vDvlfj" alt=""><figcaption></figcaption></figure>

For understanding how you can use this feature as a marketer, Please refer to our guide here.

{% embed url="<https://docs.edgetag.io/onboarding/e-commerce/scaling-playbook/product-category-and-margin-optimization#three-ways-to-transform-events>" %}


# Overview

We offer several methods to integrate our system into your site or app.

### Apps

Our app, designed for specific platforms, reduces your workload by capturing as much first-party information as possible out of the box.

{% content-ref url="/pages/qdupkkHqsi3MG3Gb2nBx" %}
[Shopify](/installation/shopify)
{% endcontent-ref %}

{% content-ref url="/pages/sYNPPgsIWY4GqgWQi9f3" %}
[Wordpress](/installation/wordpress)
{% endcontent-ref %}

### Javascript

If your platform isn't supported out of the box, don’t worry—we’ve got you covered. We offer two methods for implementing with JavaScript.&#x20;

If your platform allows you to add code snippets to the site (commonly referred to as head snippets or HTML injection), check out our [Browser](/implementation/browser) guide. It will provide you with all the options available for adding a snippet and capturing data. Make sure to check [Key Concepts](/overview/key-concepts) page before you start to understand our thinking and best practices.

{% content-ref url="/pages/QmBPs3JG72X37OB9wSII" %}
[Browser](/implementation/browser)
{% endcontent-ref %}

If you have developed your own front end using frameworks like React, Angular, or Next.js, you can utilize our NPM package suite to assist you with end-to-end integration. Check out our [Headless](/implementation/headless) guide. It will provide you with all the options available for adding a snippet and capturing data. Make sure to check [Key Concepts](/overview/key-concepts) page before you start to understand our thinking and best practices.

{% content-ref url="/pages/kiPuJwh1a5kstb6HAMbi" %}
[Headless](/implementation/headless)
{% endcontent-ref %}

### API

There are situations in which you would like to send us events directly through an HTTP request. Typically, this happens for offline conversions or delayed processing. Check out our [HTTP](/implementation/http) guide on how to send the data.

{% content-ref url="/pages/X73s5CNrNRUNqm6duJy8" %}
[HTTP](/implementation/http)
{% endcontent-ref %}


# Browser


# Initialization

{% hint style="info" %}
For optimal performance, it is recommended that you insert the code directly into your site, rather than using tag managers like Google Tag Manager. Otherwise, you may experience data losses or discrepancies. It's also recommended to put it in the header of the site
{% endhint %}

To begin using EdgeTag, you need to load our script and initialize it. The script will be loaded from our `/load` endpoint. This script is dynamically generated based on your channel configurations, ensuring that only the necessary components are included to maintain your website's optimal performance. We will **automatically include all browser pixels** that you configured.

{% hint style="warning" %}
Ensure you replace the example URL `https://d.mysite.com` with the one generated for you in the app.
{% endhint %}

{% code overflow="wrap" %}

```html
<script>
  window.edgetag=window.edgetag||function(){(edgetag.stubs=edgetag.stubs||[]).push(arguments)};

  edgetag('init', {
    edgeURL: 'https://d.mysite.com',
    disableConsentCheck: true
  })
</script>
<script async type="text/javascript" src="https://d.mysite.com/load"></script>
```

{% endcode %}

The second part of the snippet is the initialization, specifically the call to the init function. Available parameters for the init function are:

| Name                | Type    | Required | Description                                                                                     |
| ------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------- |
| edgeURL             | String  | Yes      | EdgeTag URL that you received as part of onboarding.                                            |
| disableConsentCheck | Boolean | No       | If you don't have consent on your site, you can disable it with this flag.                      |
| userId              | String  | No       | If you would like to set custom id for your first party cookie, you can pass unique identifier. |

Now that we have a snippet on the site and have initialized it, let's discuss multiple instances, as you can have multiple EdgeTag instances on the same site.


# Multiple instances

In some cases, you may need to run two different EdgeTag instances on the same shop. This requires initializing each EdgeTag with a unique EdgeTag URL. Below are some examples of how to manage this situation. We recommend including the destination in all your calls from the start if you anticipate this scenario.

{% code title="Initializing site A" %}

```html
<script>
  window.edgetag=window.edgetag||function(){(edgetag.stubs=edgetag.stubs||[]).push(arguments)};

  edgetag('init', {
    edgeURL: 'https://a.mysite.com',
    disableConsentCheck: true
  })
</script>
<script async type="text/javascript" src="https://a.mysite.com/load"></script>
```

{% endcode %}

{% code title="Initializing site B" %}

```html
<script>
  window.edgetag=window.edgetag||function(){(edgetag.stubs=edgetag.stubs||[]).push(arguments)};

  edgetag('init', {
    edgeURL: 'https://b.mysite.com',
    disableConsentCheck: true
  })
</script>
<script async type="text/javascript" src="https://b.mysite.com/load"></script>
```

{% endcode %}

{% code title="Will send PageView to both instances" %}

```javascript
edgetag('tag', 'PageView')
```

{% endcode %}

{% code title="Will send PageView only to instance A" %}

```javascript
edgetag('tag', 'PageView', {}, {}, { destination: 'https://a.mysite.com' })
```

{% endcode %}


# Events

Sending events to different channels is easy with EdgeTag. Events can be anything really: clicking on a button, submitting a form, typing in input, page load, text hover, etc.

To ensure we can support multiple channels without requiring you to worry about the payloads and the specific needs of each channel, we have created [standard events](/overview/standard-events). This approach enables us to precisely identify the data we receive on the platform and how to transform it in real-time before sending it to its final destinations.

### Parameters

| Name      | Type                                                                        | Required | Description                                                                                       |
| --------- | --------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------- |
| name      | String                                                                      | Yes      | Name of the event that you are capturing.                                                         |
| data      | Record\<string, any>                                                        | No       | Additional data that you would like to send as part of the event.                                 |
| providers | Record\<string, boolean>                                                    | No       | Define for which providers you would like to send this event. By default, we send to all of them. |
| options   | <p>{<br>method?: 'beacon',<br>sync?: true,<br>destination?: string<br>}</p> | No       | See below                                                                                         |

#### Options

**Method**: If you provide the method with a value `beacon`, we will send this event through a [beacon](https://developer.mozilla.org/en-US/docs/Web/API/Beacon_API) delivery mechanism instead of a regular fetch.

**Sync**: Setting sync to true will cause the request to wait for all channels to complete their actions before responding. This may lead to more canceled events in browsers, as some channels might need more time to respond.

**Destination**: We recommend specifying the destination, especially if multiple EdgeTag instances exist on the website. If you do not, we will trigger the same event to multiple destinations. The destination value should be the EdgeTag URL.

### Examples

{% code title="simple PageView" %}

```javascript
edgetag('tag', 'PageView')
```

{% endcode %}

{% code title="AddToCart with a payload" %}

```javascript
edgetag('tag', 'AddToCart', { value: 10.0, currency: 'USD' })
```

{% endcode %}

{% code title="PageView which we only want to send to Meta channel" %}

```javascript
edgetag('tag', 'PageView', {}, { facebook: true })
```

{% endcode %}

{% code title="PageView where you define to which destination it should be send" %}

```javascript
edgetag('tag', 'PageView', {}, {}, { destination: 'https://d.domain.com' })
```

{% endcode %}


# User info

User information is essential for attribution channels, so it’s crucial to capture it as early as possible. This information is stored in your dedicated ID graph, where, in addition to personally identifiable information (PII), we also store click IDs and any custom data you wish to include.

We have two ways of sending us user info:&#x20;

* **user**: This is used for a single property, and it only accepts standard keys.
* **data**: Allows saving multiple properties simultaneously, including custom and standard keys.

Let's start by examining what the standard keys look like.


# Standard Keys

These are standard keys that we save in our **main** ID graph table. These are the keys that can be send to `user` function that do not accept any other custom keys.

| Key         | Format                                                                                                                                                                                                                         | Example           |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------- |
| email       | lowercase and no leading or trailing spaces                                                                                                                                                                                    | <user@domain.com> |
| phone       | in E.164 format                                                                                                                                                                                                                | +14154552670      |
| firstName   | lowercase with no punctuation. If using special characters, the text must be encoded in UTF-8 format                                                                                                                           | jane              |
| lastName    | lowercase with no punctuation. If using special characters, the text must be encoded in UTF-8 format                                                                                                                           | doe               |
| gender      | f for female / m for male                                                                                                                                                                                                      | f                 |
| dateOfBirth | <p>Format is YYYYMMDD, without punctuation.<br>Year: Use the YYYY format from 1900 to current year.<br>Month: Use the MM format: 01 to 12.<br>Date: Use the DD format: 01 to 31.</p>                                           | 19950521          |
| city        | lowercase with no punctuation. If using special characters, the text must be encoded in UTF-8 format                                                                                                                           | fremont           |
| state       | [2-character ANSI abbreviation code](https://en.wikipedia.org/wiki/Federal_Information_Processing_Standard_state_code) in lowercase. States outside the U.S. should be in lowercase with no punctuation, no special characters | ca                |
| zip         | lowercase with no spaces and no dash                                                                                                                                                                                           | 94538             |
| country     | lowercase, 2-letter country code in [ISO 3166-1 Alpha 2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)                                                                                                                     | us                |
| ip          | IPv4 / IPv6                                                                                                                                                                                                                    | 117.182.233.16    |


# User

**When should I use this function?**\
Use it when you are sending only a single key and are only using [standard keys](/implementation/browser/user-info/standard-keys).&#x20;

### Parameters

| Name      | Type                                        | Required | Description                                                                                      |
| --------- | ------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------ |
| key       | String                                      | Yes      | Defines which user info you are sending, see[ Default keys](#default-keys) that you can use      |
| value     | String                                      | Yes      | Value for the specific key, make sure to match the format                                        |
| providers | Record\<string, boolean>                    | No       | Define for which providers you would like to send user data. By default, we send to all of them. |
| options   | { method?: 'beacon', destination?: string } | No       | See below                                                                                        |

#### Options

**Method**: If you provide the method with a value `beacon`, we will send this event through a [beacon](https://developer.mozilla.org/en-US/docs/Web/API/Beacon_API) delivery mechanism instead of a regular fetch.

**Destination**: We recommend specifying the destination, especially if multiple EdgeTag instances exist on the website. If you do not, we will trigger the same event to multiple destinations. The destination value should be the EdgeTag URL.

### Examples

```javascript
edgetag('user', 'email', 'john@site.com')
```

```javascript
edgetag('user', 'city', 'fremont')
```


# Data

**When should I use this function?**\
When you want to send multiple keys and custom keys in addition to the [standard keys](/implementation/browser/user-info/standard-keys).

### Parameters

| Name      | Type                                        | Required | Description                                                                                 |
| --------- | ------------------------------------------- | -------- | ------------------------------------------------------------------------------------------- |
| data      | Record\<string, string>                     | Yes      | Data that you would like to persist on the edge.                                            |
| providers | Record\<string, boolean>                    | No       | Define for which providers you would like to send data. By default, we send to all of them. |
| option    | { method?: 'beacon', destination?: string } | No       | See below                                                                                   |

#### Options

**Method**: If you provide the method with a value `beacon`, we will send this event through a [beacon](https://developer.mozilla.org/en-US/docs/Web/API/Beacon_API) delivery mechanism instead of a regular fetch.

**Destination**: We recommend specifying the destination, especially if multiple EdgeTag instances exist on the website. If you do not, we will trigger the same event to multiple destinations. The destination value should be the EdgeTag URL.

### Examples

{% code title="Multiple standard keys" %}

```javascript
edgetag('data', {
  email: 'john@site.com',
  phone: '+14154552670',
  city: 'fremont'
})

```

{% endcode %}

{% code title="Multiple custom keys" %}

```javascript
edgetag('data', {
  member: true,
  step: 10
})
```

{% endcode %}

{% code title="Mixture of standard and custom keys" %}

```javascript
edgetag('data', {
  email: 'john@site.com'
  step: 10
})
```

{% endcode %}


# Get Data

This API allows you to retrieve data from the edge storage that you send via the user or data function.

{% hint style="info" %}
For standard keys, we will return a boolean value indicating whether the key is present or not. For example, for an email address, we would return **emailExists** with a corresponding boolean value.
{% endhint %}

### Parameters

| Name     | Type                                    | Required | Description                                             |
| -------- | --------------------------------------- | -------- | ------------------------------------------------------- |
| keys     | String\[]                               | Yes      | Keys that you would like to get value of from the edge. |
| callback | (data: Record\<string, string>) => void | Yes      | Callback function that gives you results.               |
| options  | { destination?: string }                | No       | See below                                               |

#### Options

**Destination**: We recommend specifying the destination, especially if multiple EdgeTag instances exist on the website. If you do not, we will trigger the same event to multiple destinations. The destination value should be the EdgeTag URL.

### Example

```javascript
edgetag('getData', ['email', 'step'], (data) => {
  console.log(data) // it will print out { emailExists: true, step: '10' }
})
```


# Consent

Consent is essential to us because we respect users' privacy and strive to protect it. This commitment is one of the primary reasons we developed this product. All events sent from our system are protected by consent, meaning we do not share any data without proper consent.

The consent function should be called every time the user changes the consent.

{% hint style="warning" %}
You need to provide consent for each provider or consent category. At least one needs to be provided!
{% endhint %}

### Parameters

| Name       | Type                             | Required | Description                                                                        |
| ---------- | -------------------------------- | -------- | ---------------------------------------------------------------------------------- |
| consent    | Record\<string, boolean> \| null | Yes / No | Tells which provider was consented to and which not.                               |
| categories | Record\<string, boolean>         | Yes / No | No Tells which categories are consented to and which are not. See categories below |
| options    | { destination?: string }         | No       | See below                                                                          |

#### Categories

* necessary
* advertising
* analtyics
* share\_pii
* functional

#### Options

**Destination**: We recommend specifying the destination, especially if multiple EdgeTag instances exist on the website. If you do not, we will trigger the same event to multiple destinations. The destination value should be the EdgeTag URL.

### Examples

{% code title="Accepting all channels" %}

```javascript
edgetag('consent', { all: true })
```

{% endcode %}

{% code title="Accepting only specific channels" %}

```javascript
edgetag('consent', { facebook: true, googleAnalytics4: false })
```

{% endcode %}

{% code title="Specific channels and categories" %}

```javascript
edgetag('consent', 
  { all: false, facebook: true, tiktok: true, googleAnalytics4: false }, 
  { all: false, necessary: true, advertising: true, analytics: false }
)
```

{% endcode %}

{% code title="Specifying only categories " %}

```javascript
edgetag('consent', null, { all: false, necessary: true, advertising: true })
```

{% endcode %}


# Get Consent

This API allows you to retrieve the consent value provided through the consent function.

### Parameters

| Name     | Type                                 | Required       | Description |                                                                                  |     |                                                                                                                                                                                               |
| -------- | ------------------------------------ | -------------- | ----------- | -------------------------------------------------------------------------------- | --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| callback | <p>(consent: Record\<string, boolean | Record\<string | boolean>>   | null, error?: Error,<br>consentCategories: Record\<string, boolean>) => void</p> | Yes | Callback function that gives you the consent value of the user. If consent is set to null in the callback which indicates that something went wrong, and access error param for more details. |
| options  | { destination?: string }             | No             |             |                                                                                  |     |                                                                                                                                                                                               |
|          |                                      |                |             |                                                                                  |     |                                                                                                                                                                                               |

#### Options

**Destination**: We recommend specifying the destination, especially if multiple EdgeTag instances exist on the website. If you do not, we will trigger the same event to multiple destinations. The destination value should be the EdgeTag URL.

### Examples

{% code title="When consent exists" %}

```javascript
edgetag('getConsent', (consent, error, consentCategories) => {
  console.log(consent) // { all: false, facebook: { facebook1: true, facebook2: false }}
  console.log(error) // undefined
  console.log(consentCategories) // { all: false, marketing: true}
})
```

{% endcode %}

{% code title="No consent set yet" %}

```javascript
edgetag('getConsent', (consent, error, consentCategories) => {
  console.log(consent) // null
  console.log(error) // "Error: Consent Not Found for current user"
  console.log(consentCategories) // null
})
```

{% endcode %}


# User ID

You can use this function to retrieve the current User ID from EdgeTag.

```javascript
const userId = edgetag('getUserId')
```


# Configuration

You can use this function to configure the SDK if you wish.

Parameters

| Name         | Type   | Required | Description                                                                  |
| ------------ | ------ | -------- | ---------------------------------------------------------------------------- |
| pageUrl      | String | No       | If you would like to send custom page url                                    |
| destionation | string | No       | If you would like to set configuration only for a specific EdgeTag instance. |

Examples

{% code title="For all instances" %}

```javascript
edgetag('config', { pageUrl: 'https://mysite.com/page' })
```

{% endcode %}

{% code title="Set configuration for a specific instance" %}

```javascript
edgetag('config', 
{ pageUrl: 'https://mysite.com/page', destination: 'https://d.mysite.com' }
)
```

{% endcode %}


# Ready event

In cases where you would like to ensure that code runs after a tag has been initialized, you can use the **ready** event to register a callback that runs for every tag initialization.

In contrast to the [edgetag-initialized](/implementation/browser/browser-events) HTML custom event, this callback will also be fired if the callback is registered after the tag has already initialized, ensuring that it will always fire regardless of when it is registered.

### Properties

| Property          | Description                                                                  |
| ----------------- | ---------------------------------------------------------------------------- |
| destination       | EdgeTag URL for the tag that has been initialized                            |
| userId            | user ID                                                                      |
| sessionId         | current session ID                                                           |
| isNewUser         | true if this is the user's first visit to the site                           |
| isNewSession      | true if this is the start of the user's session on this site                 |
| consent           | Describes the user's consent configuration and is opt-in.                    |
| consentCategories | Describes the user's consent category configuration and is opt-in.           |
| consentSettings   | Describes the channel categories and whether consent is enabled for the tag. |

### Example

```javascript
edgetag('ready', (params) => {
  if (params.isNewUser) {
    // this will only ever trigger once on the first visit
    window.alert('Hello and welcome to our shop!')
  } else if (params.isNewSession) {
    // this will only trigger on subsequent visits once per session
    window.alert('Hi and welcome back to our shop!')
  }
})
```


# Browser events

We provide some custom event triggers that you can listen to in JavaScript.

### Initialization

This will be triggered when the SDK is initialized.

**Event name**\
edgetag-initialized

{% code title="Event definiton" %}

```typescript
{
  destination: string, 
  userId: string, 
  isNewUser: boolean | undefined, 
  geoCountry: string,
  geoRegion: string,
  session: Record<string, string[]>,
  userIP: string,
  consent: Record<string, boolean | Record<string | boolean>> | null, 
  consentCategories: Record<string, boolean>,
  consentSetting: Record<string, string[]>
}
```

{% endcode %}

{% code title="Example" %}

```javascript
window.addEventListener('edgetag-initialized', ({ detail }) => {
  console.log({
    userId: detail.userId,
    isNewUser: detail.isNewUser,
    consent: detail.consent,
    ip: detail.userIP,
    consentSetting: detail.consentSetting
  })
})
```

{% endcode %}

### Consent

This will be triggered when the consent function is called

**Event name**\
edgetag-consent

{% code title="Event definition" %}

```typescript
{ 
  destination: string, 
  oldConsent: Record<string, boolean | Record<string | boolean>> | null
  newConsent: Record<string, boolean | Record<string | boolean>> | null
  oldConsentCategories: Record<string, boolean> | null
  newConsentCategories: Record<string, boolean> | null
}
```

{% endcode %}

{% code title="Example" %}

```javascript
window.addEventListener('edgetag-consent', ({ detail }) => {
  console.log({
    oldConsent: detail.oldConsent,
    newConsent: detail.newConsent
  })
})
```

{% endcode %}


# Headless


# Initialization

To begin using EdgeTag, you need to add our [NPM package](https://www.npmjs.com/package/@blotoutio/edgetag-sdk-js) and initialize it. You can add our package to your project by running the following command:

```bash
npm i @blotoutio/edgetag-sdk-js
```

Now that our package is installed we need to initialize it. Our package should be initialized only once per full reload (do not initialize it when doing navigation with JS router).

{% hint style="warning" %}
Ensure you replace the example URL `https://d.mysite.com` with the one generated for you in the app.
{% endhint %}

{% code overflow="wrap" %}

```javascript
import { init } from '@blotoutio/edgetag-sdk-js'

init({
  edgeURL: 'https://d.mysite.com',
  disableConsentCheck: true
})
```

{% endcode %}

Available parameters for the init function are:

| Name                | Type        | Required | Description                                                                                     |
| ------------------- | ----------- | -------- | ----------------------------------------------------------------------------------------------- |
| edgeURL             | String      | Yes      | EdgeTag URL that you received as part of onboarding.                                            |
| disableConsentCheck | Boolean     | No       | If you don't have consent on your site, you can disable it with this flag.                      |
| userId              | String      | No       | If you would like to set custom id for your first party cookie, you can pass unique identifier. |
| providers           | Provider\[] | No       | Browser pixels that need to be added for specific channels                                      |

{% hint style="warning" %}
If you would like to add any browser pixels/scripts (such as a Meta pixel), you need to install the corresponding npm packages for that specific channel. See [Browser packages](/implementation/headless/browser-packages) docs for more info!
{% endhint %}

{% code title="Initialization with browser pixel added" %}

```javascript
import { init } from '@blotoutio/edgetag-sdk-js'
import facebook from '@blotoutio/providers-facebook-sdk'

init({
  edgeURL: 'https://d.mysite.com',
  disableConsentCheck: true,
  providers: [facebook]
})
```

{% endcode %}

Now that we have a snippet on the site and have initialized it, let's discuss multiple instances, as you can have multiple EdgeTag instances on the same site.


# Multiple instances

In some cases, you may need to run two different EdgeTag instances on the same shop. This requires initializing each EdgeTag with a unique EdgeTag URL. Below are some examples of how to manage this situation. We recommend including the destination in all your calls from the start if you anticipate this scenario.

{% code title="Initializing site A and B" %}

```javascript
import { init } from '@blotoutio/edgetag-sdk-js'

init({
    edgeURL: 'https://a.mysite.com',
    disableConsentCheck: true
})

init({
    edgeURL: 'https://b.mysite.com',
    disableConsentCheck: true
})
```

{% endcode %}

{% code title="Will send PageView to both instances" %}

```javascript
import { tag } from '@blotoutio/edgetag-sdk-js'

tag('PageView')
```

{% endcode %}

{% code title="Will send PageView only to instance A" %}

```javascript
import { tag } from '@blotoutio/edgetag-sdk-js'

tag('PageView', {}, {}, { destination: 'https://a.mysite.com' })
```

{% endcode %}


# Events

Sending events to different channels is easy with EdgeTag. Events can be anything really: clicking on a button, submitting a form, typing in input, page load, text hover, etc.

To ensure we can support multiple channels without requiring you to worry about the payloads and the specific needs of each channel, we have created [standard events](/overview/standard-events). This approach enables us to precisely identify the data we receive on the platform and how to transform it in real-time before sending it to its final destinations.

### Parameters

| Name      | Type                                                                        | Required | Description                                                                                       |
| --------- | --------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------- |
| name      | String                                                                      | Yes      | Name of the event that you are capturing.                                                         |
| data      | Record\<string, any>                                                        | No       | Additional data that you would like to send as part of the event.                                 |
| providers | Record\<string, boolean>                                                    | No       | Define for which providers you would like to send this event. By default, we send to all of them. |
| options   | <p>{<br>method?: 'beacon',<br>sync?: true,<br>destination?: string<br>}</p> | No       | See below                                                                                         |

#### Options

**Method**: If you provide the method with a value `beacon`, we will send this event through a [beacon](https://developer.mozilla.org/en-US/docs/Web/API/Beacon_API) delivery mechanism instead of a regular fetch.

**Sync**: Setting sync to true will cause the request to wait for all channels to complete their actions before responding. This may lead to more canceled events in browsers, as some channels might need more time to respond.

**Destination**: We recommend specifying the destination, especially if multiple EdgeTag instances exist on the website. If you do not, we will trigger the same event to multiple destinations. The destination value should be the EdgeTag URL.

### Examples

{% code title="simple PageView" %}

```javascript
import { tag } from '@blotoutio/edgetag-sdk-js'

tag('PageView')
```

{% endcode %}

{% code title="AddToCart with a payload" %}

```javascript
import { tag } from '@blotoutio/edgetag-sdk-js'

tag('AddToCart', { value: 10.0, currency: 'USD' })
```

{% endcode %}

{% code title="PageView which we only want to send to Meta channel" %}

```javascript
import { tag } from '@blotoutio/edgetag-sdk-js'

tag('PageView', {}, { facebook: true })
```

{% endcode %}

{% code title="PageView where you define to which destination it should be send" %}

```javascript
import { tag } from '@blotoutio/edgetag-sdk-js'

tag('PageView', {}, {}, { destination: 'https://d.domain.com' })
```

{% endcode %}


# User info

User information is essential for attribution channels, so it’s crucial to capture it as early as possible. This information is stored in your dedicated ID graph, where, in addition to personally identifiable information (PII), we also store click IDs and any custom data you wish to include.

We have two ways of sending us user info:&#x20;

* **user**: This is used for a single property, and it only accepts standard keys.
* **data**: Allows saving multiple properties simultaneously, including custom and standard keys.

Let's start by examining what the standard keys look like.


# Standard Keys

These are standard keys that we save in our **main** ID graph table. These are the keys that can be send to `user` function that do not accept any other custom keys.

| Key         | Format                                                                                                                                                                                                                         | Example           |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------- |
| email       | lowercase and no leading or trailing spaces                                                                                                                                                                                    | <user@domain.com> |
| phone       | in E.164 format                                                                                                                                                                                                                | +14154552670      |
| firstName   | lowercase with no punctuation. If using special characters, the text must be encoded in UTF-8 format                                                                                                                           | jane              |
| lastName    | lowercase with no punctuation. If using special characters, the text must be encoded in UTF-8 format                                                                                                                           | doe               |
| gender      | f for female / m for male                                                                                                                                                                                                      | f                 |
| dateOfBirth | <p>Format is YYYYMMDD, without punctuation.<br>Year: Use the YYYY format from 1900 to current year.<br>Month: Use the MM format: 01 to 12.<br>Date: Use the DD format: 01 to 31.</p>                                           | 19950521          |
| city        | lowercase with no punctuation. If using special characters, the text must be encoded in UTF-8 format                                                                                                                           | fremont           |
| state       | [2-character ANSI abbreviation code](https://en.wikipedia.org/wiki/Federal_Information_Processing_Standard_state_code) in lowercase. States outside the U.S. should be in lowercase with no punctuation, no special characters | ca                |
| zip         | lowercase with no spaces and no dash                                                                                                                                                                                           | 94538             |
| country     | lowercase, 2-letter country code in [ISO 3166-1 Alpha 2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)                                                                                                                     | us                |
| ip          | IPv4 / IPv6                                                                                                                                                                                                                    | 117.182.233.16    |


# User

**When should I use this function?**\
Use it when you are sending only a single key and are only using [standard keys](/implementation/browser/user-info/standard-keys).&#x20;

### Parameters

| Name      | Type                                        | Required | Description                                                                                      |
| --------- | ------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------ |
| key       | String                                      | Yes      | Defines which user info you are sending, see[ Default keys](#default-keys) that you can use      |
| value     | String                                      | Yes      | Value for the specific key, make sure to match the format                                        |
| providers | Record\<string, boolean>                    | No       | Define for which providers you would like to send user data. By default, we send to all of them. |
| options   | { method?: 'beacon', destination?: string } | No       | See below                                                                                        |

#### Options

**Method**: If you provide the method with a value `beacon`, we will send this event through a [beacon](https://developer.mozilla.org/en-US/docs/Web/API/Beacon_API) delivery mechanism instead of a regular fetch.

**Destination**: We recommend specifying the destination, especially if multiple EdgeTag instances exist on the website. If you do not, we will trigger the same event to multiple destinations. The destination value should be the EdgeTag URL.

### Examples

```javascript
import { user } from '@blotoutio/edgetag-sdk-js'

user('email', 'john@site.com')
```

<pre class="language-javascript"><code class="lang-javascript"><strong>import { user } from '@blotoutio/edgetag-sdk-js'
</strong><strong>
</strong><strong>user('city', 'fremont')
</strong></code></pre>


# Data

**When should I use this function?**\
When you want to send multiple keys and custom keys in addition to the [standard keys](/implementation/browser/user-info/standard-keys).

### Parameters

| Name      | Type                                        | Required | Description                                                                                 |
| --------- | ------------------------------------------- | -------- | ------------------------------------------------------------------------------------------- |
| data      | Record\<string, string>                     | Yes      | Data that you would like to persist on the edge.                                            |
| providers | Record\<string, boolean>                    | No       | Define for which providers you would like to send data. By default, we send to all of them. |
| option    | { method?: 'beacon', destination?: string } | No       | See below                                                                                   |

#### Options

**Method**: If you provide the method with a value `beacon`, we will send this event through a [beacon](https://developer.mozilla.org/en-US/docs/Web/API/Beacon_API) delivery mechanism instead of a regular fetch.

**Destination**: We recommend specifying the destination, especially if multiple EdgeTag instances exist on the website. If you do not, we will trigger the same event to multiple destinations. The destination value should be the EdgeTag URL.

### Examples

{% code title="Multiple standard keys" %}

```javascript
import { data } from '@blotoutio/edgetag-sdk-js'

data({
  email: 'john@site.com',
  phone: '+14154552670',
  city: 'fremont'
})

```

{% endcode %}

<pre class="language-javascript" data-title="Multiple custom keys"><code class="lang-javascript"><strong>import { data } from '@blotoutio/edgetag-sdk-js'
</strong><strong>
</strong><strong>data({
</strong>  member: true,
  step: 10
})
</code></pre>

{% code title="Mixture of standard and custom keys" %}

```javascript
import { data } from '@blotoutio/edgetag-sdk-js'

data({
  email: 'john@site.com'
  step: 10
})
```

{% endcode %}


# Get Data

This API allows you to retrieve data from the edge storage that you send via the user or data function.

{% hint style="info" %}
For standard keys, we will return a boolean value indicating whether the key is present or not. For example, for an email address, we would return **emailExists** with a corresponding boolean value.
{% endhint %}

### Parameters

| Name     | Type                                    | Required | Description                                             |
| -------- | --------------------------------------- | -------- | ------------------------------------------------------- |
| keys     | String\[]                               | Yes      | Keys that you would like to get value of from the edge. |
| callback | (data: Record\<string, string>) => void | Yes      | Callback function that gives you results.               |
| options  | { destination?: string }                | No       | See below                                               |

#### Options

**Destination**: We recommend specifying the destination, especially if multiple EdgeTag instances exist on the website. If you do not, we will trigger the same event to multiple destinations. The destination value should be the EdgeTag URL.

### Example

```javascript
import { getData } from '@blotoutio/edgetag-sdk-js'

getData(['email', 'step'], (data) => {
  console.log(data) // it will print out { emailExists: true, step: '10' }
})
```


# Consent

Consent is essential to us because we respect users' privacy and strive to protect it. This commitment is one of the primary reasons we developed this product. All events sent from our system are protected by consent, meaning we do not share any data without proper consent.

The consent function should be called every time the user changes the consent.

{% hint style="warning" %}
You need to provide consent for each provider or consent category. At least one needs to be provided!
{% endhint %}

### Parameters

| Name       | Type                             | Required | Description                                                                        |
| ---------- | -------------------------------- | -------- | ---------------------------------------------------------------------------------- |
| consent    | Record\<string, boolean> \| null | Yes / No | Tells which provider was consented to and which not.                               |
| categories | Record\<string, boolean>         | Yes / No | No Tells which categories are consented to and which are not. See categories below |
| options    | { destination?: string }         | No       | See below                                                                          |

#### Categories

* necessary
* advertising
* analtyics
* share\_pii
* functional

#### Options

**Destination**: We recommend specifying the destination, especially if multiple EdgeTag instances exist on the website. If you do not, we will trigger the same event to multiple destinations. The destination value should be the EdgeTag URL.

### Examples

{% code title="Accepting all channels" %}

```javascript
import { consent } from '@blotoutio/edgetag-sdk-js'

consent({ all: true })
```

{% endcode %}

{% code title="Accepting only specific channels" %}

```javascript
import { consent } from '@blotoutio/edgetag-sdk-js'

consent({ facebook: true, googleAnalytics4: false })
```

{% endcode %}

{% code title="Specific channels and categories" %}

```javascript
import { consent } from '@blotoutio/edgetag-sdk-js'

consent( 
  { all: false, facebook: true, tiktok: true, googleAnalytics4: false }, 
  { all: false, necessary: true, advertising: true, analytics: false }
)
```

{% endcode %}

{% code title="Specifying only categories " %}

```javascript
import { consent } from '@blotoutio/edgetag-sdk-js'

consent(null, { all: false, necessary: true, advertising: true })
```

{% endcode %}


# Get Consent

This API allows you to retrieve the consent value provided through the consent function.

### Parameters

| Name     | Type                                 | Required       | Description |                                                                                  |     |                                                                                                                                                                                               |
| -------- | ------------------------------------ | -------------- | ----------- | -------------------------------------------------------------------------------- | --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| callback | <p>(consent: Record\<string, boolean | Record\<string | boolean>>   | null, error?: Error,<br>consentCategories: Record\<string, boolean>) => void</p> | Yes | Callback function that gives you the consent value of the user. If consent is set to null in the callback which indicates that something went wrong, and access error param for more details. |
| options  | { destination?: string }             | No             |             |                                                                                  |     |                                                                                                                                                                                               |
|          |                                      |                |             |                                                                                  |     |                                                                                                                                                                                               |

#### Options

**Destination**: We recommend specifying the destination, especially if multiple EdgeTag instances exist on the website. If you do not, we will trigger the same event to multiple destinations. The destination value should be the EdgeTag URL.

### Examples

{% code title="When consent exists" %}

```javascript
import { getConsent } from '@blotoutio/edgetag-sdk-js'

getConsent((consent, error, consentCategories) => {
  console.log(consent) // { all: false, facebook: { facebook1: true, facebook2: false }}
  console.log(error) // undefined
  console.log(consentCategories) // { all: false, marketing: true}
})
```

{% endcode %}

{% code title="No consent set yet" %}

```javascript
import { getConsent } from '@blotoutio/edgetag-sdk-js'

getConsent((consent, error, consentCategories) => {
  console.log(consent) // null
  console.log(error) // "Error: Consent Not Found for current user"
  console.log(consentCategories) // null
})
```

{% endcode %}


# User ID

You can use this function to retrieve the current User ID from EdgeTag.

```javascript
import { getUserId } from '@blotoutio/edgetag-sdk-js'

const userId = getUserId()
```


# Configuration

You can use this function to configure the SDK if you wish.

Parameters

| Name         | Type   | Required | Description                                                                  |
| ------------ | ------ | -------- | ---------------------------------------------------------------------------- |
| pageUrl      | String | No       | If you would like to send custom page url                                    |
| destionation | string | No       | If you would like to set configuration only for a specific EdgeTag instance. |

Examples

{% code title="For all instances" %}

```javascript
import { config } from '@blotoutio/edgetag-sdk-js'

config({ pageUrl: 'https://mysite.com/page' })
```

{% endcode %}

{% code title="Set configuration for a specific instance" %}

```javascript
import { config } from '@blotoutio/edgetag-sdk-js'

config({ pageUrl: 'https://mysite.com/page', destination: 'https://d.mysite.com' })
```

{% endcode %}


# Ready event

In cases where you would like to ensure that code runs after a tag has been initialized, you can use the **ready** event to register a callback that runs for every tag initialization.

In contrast to the [edgetag-initialized](/implementation/browser/browser-events) HTML custom event, this callback will also be fired if the callback is registered after the tag has already initialized, guaranteeing that it will always fire regardless of when it is registered.

### Properties

| Property          | Description                                                                  |
| ----------------- | ---------------------------------------------------------------------------- |
| destination       | EdgeTag URL for the tag that has been initialized                            |
| userId            | user ID                                                                      |
| sessionId         | current session ID                                                           |
| isNewUser         | true if this is the user's first visit to the site                           |
| isNewSession      | true if this is the start of the user's session on this site                 |
| consent           | Describes the user's consent configuration and is opt-in by default.         |
| consentCategories | Describes the user's consent category configuration and is opt-in.           |
| consentSettings   | Describes the channel categories and whether consent is enabled for the tag. |

### Example

```javascript
import { ready } from '@blotoutio/edgetag-sdk-js'
 
ready((params) => {
  if (params.isNewUser) {
    // this will only ever trigger once on the first visit
    window.alert('Hello and welcome to our shop!')
  } else if (params.isNewSession) {
    // this will only trigger on subsequent visits once per session
    window.alert('Hi and welcome back to our shop!')
  }
})
```


# Browser packages

With a Headless implementation, you have complete control over the code by utilizing our NPM packages. This also means that you can determine which browser pixel will be loaded. Regardless of your configuration settings in the channel to enable the browser pixel, it will NOT function until you add the specific NPM package for that channel.

Packages will still verify whether you have browser pixel enabled for a specific channel. They will only load third-party scripts if this feature is enabled. This means that if you have the pixel installed but it is disabled in our configuration, you will not experience any performance degradation.

Each channel's documentation will have a link to the package on the first page.

Now, let's look at how to add the browser package for the Meta channel.

{% code title="Initialization with browser pixel added" %}

```javascript
import { init } from '@blotoutio/edgetag-sdk-js'
import facebook from '@blotoutio/providers-facebook-sdk'

init({
  edgeURL: 'https://d.mysite.com',
  disableConsentCheck: true,
  providers: [facebook]
})
```

{% endcode %}


# HTTP

If you need to send events from an offline system or have events triggered outside your site, you can use our HTTP approach to send them.

EdgeTag operates using a server-side cookie to identify users. Therefore, it is important, if possible, to store our ID from the browser in our system. This will allow you to associate the same user when sending events via HTTP.

If the system is not connected to your website, there's no need to worry. We can also connect users via email. Please ensure that you include the email in the payload using the `userEmail` property. We will search for this email in our ID graph and connect it to the user. If the user is not found, we will create a new user.

{% hint style="info" %}
If you are using EdgeTag without a browser, you can always define your own EdgeTag ID and use that as an identifier. Example: You use EdgeTag in your mobile app, and you use mobile ID as EdgeTag ID when sending events.
{% endhint %}

### Error handling

All APIs have the same error-handling mechanism.

```java
{
  "message": "Value is not defined",
  "code": "400"
}
```


# Event

The URL for your endpoint is available in [our app dashboard](https://app.edgetag.io/).

### User

When sending events, make sure that you handle the user correctly. You have two options for connecting a user.

#### Via EdgeTag ID

If you have the option, it's preferred to store our EdgeTag ID (you can use our `getUserId` function) from the browser into your own system database. This way, when you send events from your system to EdgeTag, you will include them in the request headers  as  `EdgeTagUserId`.

<pre class="language-bash" data-title="With EdgeTag ID" data-overflow="wrap"><code class="lang-bash">curl --request POST \
  --url https://abc.domain.com/tag \
  --header 'Content-Type: application/json' \
<strong>  --header 'EdgeTagUserId: 138fffcd-ee39-4fd1-b5b5-760f07454407-1662025231518' \
</strong>  --data '{
	"data": {
		"value": 10.50,
		"currency": "USD",
		"contents": [
			{
				"id": "123123123",
				"quantity": 1,
				"item_price": 10.50,
				"title": "Summer Fun",
				"category": "bracelets",
				"image": "https://domain.com/product/fun-main.jpg",
				"url": "https://domain.com/summer-fun"
			}
		]
	},
	"eventId": "c2RrX3N0YXJ0-72a6410e-4b6d-4bfa-be5f-d17c294c73bc-1285.1000",
	"eventName": "AddToCart",
	"pageTitle": "T-Shirt",
	"pageUrl": "https://domain.com/product/shirt",
	"providers": {
		"all": true
	},
	"referrer": "https://google.com",
	"search": "?utm_campaign=online",
	"timestamp": 1671443880281,
	"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36 Brave",
	"storage": {
		"edgeTag": {
			"consent": {
				"all": true
			}
		}
	}
}'
</code></pre>

#### Via Email

If you only have an email, there's nothing to worry about. We can integrate the browser (online) with your offline system. Make sure that you provide an email in the payload through `userEmail` key.

<pre class="language-bash" data-title="With Email" data-overflow="wrap"><code class="lang-bash">curl --request POST \
  --url https://abc.domain.com/tag \
  --header 'Content-Type: application/json' \
  --data '{
	"data": {
		"value": 10.50,
		"currency": "USD",
		"contents": [
			{
				"id": "123123123",
				"quantity": 1,
				"item_price": 10.50,
				"title": "Summer Fun",
				"category": "bracelets",
				"image": "https://domain.com/product/fun-main.jpg",
				"url": "https://domain.com/summer-fun"
			}
		],
<strong>		"userEmail": "user@gmail.com"
</strong>	},
	"eventId": "c2RrX3N0YXJ0-72a6410e-4b6d-4bfa-be5f-d17c294c73bc-1285.1000",
	"eventName": "AddToCart",
	"pageTitle": "T-Shirt",
	"pageUrl": "https://domain.com/product/shirt",
	"providers": {
		"all": true
	},
	"referrer": "https://google.com",
	"search": "?utm_campaign=online",
	"timestamp": 1671443880281,
	"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36 Brave",
	"storage": {
		"edgeTag": {
			"consent": {
				"all": true
			}
		}
	}
}'
</code></pre>

### Sending Event

If you are sending standard events, please ensure that you follow our [standard events documentation](/overview/standard-events).

When consent is enabled, ensure that you include it in the request as well; see the examples above (`storage` key).

The Event ID should be unique to prevent signal loss.


# Data

Enriching our ID graph is crucial, so adding more data to it is highly beneficial.&#x20;

{% hint style="warning" %}
You can only send data if you have an EdgeTag ID from the user.
{% endhint %}

You can send [standard keys](/implementation/browser/user-info/standard-keys) or custom keys. Custom keys can be used in the browser to modify a site or adjust funnels based on this key. All of this data can be retrieved in the browser!

```bash
curl --request POST \
  --url https://abc.domain.com/data \
  --header 'Content-Type: application/json' \
  --header 'EdgeTagUserId: 138fffcd-ee39-4fd1-b5b5-760f07454407-1662025231518' \
  --data '{
	"data": {
		"email": "jsmith@test.com",
		"firstName": "John",
		"lastName": "Smith",
		"color": "Blue",
		"step": "5"
	}
}'
```


# Validation

Validating implementations is crucial, which is why we developed several tools to assist you in the process. Let's explore them.

### Browser validation

A good starting point for validating integration is to begin in the browser. The following video offers valuable insights on how to get started, what to check, and how to proceed.

{% embed url="<https://www.loom.com/share/bf0918070dc54455af4bd2d64161c470>" %}

### Server validation

In the video below, we demonstrate how to test events as they arrive at our edge server.

{% embed url="<https://www.loom.com/share/38edb4f89835451aae234215660d773c>" %}

### Payload Validation

If you want to enable automated payload validation while adding support for EdgeTag to your site, consider adding a [Payload Validator](/channels/payload-validator) channel to perform these checks automatically.

{% hint style="warning" %}
Please ensure that you remove it after implementation is complete.
{% endhint %}


# Server-side cookie

A server-side cookie, or HTTP cookie, is sent from the server to the browser. You can read more about it in the [web documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies). It’s important for the system to send data from the server because browsers can verify its origin and extend its validity. For platforms like Shopify, WordPress, and others, we’ve got you covered —we automatically generate this cookie for you, and it originates from the server where your site is served. However, for headless sites, we don’t have access to your server, so we recommend that you create it yourself. Our system will still generate a server-side cookie with the EdgeTag user ID, but it’s beneficial to have both the server-side cookie and ours running simultaneously. We will map your server-side cookie to ours behind the scenes.

### Cookie options

Important options:

* Name: cookie name. Make sure that you use a value that is not already used on your site or by any other provider that you have on your site.
* Domain: For which domain should the cookie be set? It's good to set it as .mysite.com so that it works with subdomains as well
* Expires - when the cookie should expire
* SameSite - in which context the cookie should live
* Secure - if the cookie should be only served on https
* HttpOnly - should the cookie be only available for requests

You can read more about it on the [web docs.](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie)

### How to create a cookie?

Server-side cookies should be included in the document response headers. This way, we know they came from the correct server.

{% hint style="warning" %}
A cookie should only be created if it doesn't exist yet.
{% endhint %}

Below is an example of how to create a server-side cookie on your end:

```javascript
const crypto = require('crypto');

const cookieName = 'truid'; // this is just an example name, you can change it to what you want
const userId = `${crypto.randomUUID()}-${Date.now()}`;
const expirationDate = new Date(2037, 11, 20).toUTCString();
const domainCookie = '.mysite.com'; // change mysite.com to your domain

const cookieString = `${cookieName}=${userId}; SameSite=LAX; Expires=${expirationDate}; Domain=${domainCookie}; HttpOnly; Secure`;

// add cookieString to your headers under key set-cookie
response.writeHead(200, {
    "Set-Cookie": cookieString
});

```


# Velocity


# Cart Recovery


# Emotive Attribution


# Visitor Edge


# Shopify

A CRM connection is crucial for maintaining the quality and delivery of signals. It serves two main purposes:\
\
1\. **Customer Activity Tracking**: We analyze past orders to determine whether a customer is a first-time buyer or a repeat customer.\
&#x20;  \
2\. **Backup Mechanism**: In case of a failure in the browser, we can directly retrieve purchase data from Shopify, allowing us to link attribution to EdgeTag data effectively.

{% hint style="warning" %}
For the NC/RC feature to work, this step is necessary.
{% endhint %}

### How does it work?

We check for  orders every 15 minutes. When you first add a channel, we will review the last 15 minutes for any orders. After the first successful synchronization, we will store the timestamp of the last synced order in the database. \
\
During subsequent checks, we will look for orders between the timestamp of the last synced order and the current time. If there are any new orders, we will update the database with the timestamp of the latest synced order. \
\
This process will be repeated every 15 minutes to ensure that no orders are missed.


# Onboarding

With the 25th March release, Shopify CRM will be automatically configured on your tag when you install the Blotout Edgetag App on Shopify. \
\
For older tags, on the other hand, you need to go to Blotout Edgetag App on Shopify and click Update when prompted on the below screen.

<figure><img src="/files/0SLyc434txd9PVWFKbUK" alt=""><figcaption></figcaption></figure>


# API

Below you will find the keys to generate secret payloads for adding or updating a channel via the [Script API](/api/api/script).

### SHOPIFY\_STORE\_ID

In this secret, you would store the store ID associated with the CRM.\
\
**Example value**\
mystore\
\
**Type**\
IDENTIFIER\
\
**Optional**\
false

### SHOPIFY\_APP\_CLIENT\_ID

In this secret, you would store the Client ID associated with your custom app. \
\
**Example value**\
04774905dd939deb4567e7ec5e4ef234\
\
**Type**\
IDENTIFIER\
\
**Optional**\
false

### SHOPIFY\_APP\_SECRET

In this secret, you would store the Client Secret associated with your custom app. \
\
**Example value**\
shpss\_k2c1571b43c34567e618b507bf36f4rd\
\
**Type**\
PASSWORD\
\
**Optional**\
false

### SHOPIFY\_SALES\_CHANNELS

In this secret, you would define which online sales channels you would like to use the backup feature on, comma-separated.\
\
**Example value**\
580111,600143\
\
**Type**\
TEXT\
\
**Optional**\
true

### SHOPIFY\_OFFLINE\_SALES\_CHANNELS

In this secret, you would define which offline sales channels you would like to use the backup feature on, comma-separated.\
\
**Example value**\
530110\
\
**Type**\
TEXT\
\
**Optional**\
true


# WooCommerce

A CRM connection is crucial for maintaining the quality and delivery of signals. It serves two main purposes:\
\
1\. **Customer Activity Tracking**: We analyze past orders to determine whether a customer is a first-time buyer or a repeat customer.\
&#x20;  \
2\. **Backup Mechanism**: In case of a failure in the browser, we can directly retrieve purchase data from Shopify, allowing us to link attribution to EdgeTag data effectively.

{% hint style="warning" %}
For the NC/RC feature to work, this step is necessary.
{% endhint %}


# Onboarding

Connecting WooCommerce CRM is straightforward; we need your shop name and consumer information, and we'll handle the rest. After you obtain all the information required for the form, paste it into the form and click Save & Deploy.

<figure><img src="/files/wtGBFYq29oEac0wOZYO5" alt=""><figcaption></figcaption></figure>

### Shop name

You can get Shop Name from the URL of your WooCommerce website.

If your WooCommerce website URL is: <https://example.com>, your Shop Name is "example.com".

### Consumer information

To access consumer information, go to your WooCommerce dashboard, click on "Settings," then select "Advanced," and finally navigate to "REST API." From there, click on "Add Key."

<figure><img src="/files/wkTTwzVQrvSgK5zkkIaD" alt=""><figcaption></figcaption></figure>

Enter the Description (example "EdgeTag CRM"), set the permissions to Read/Write, and then click on "Generate API key".

<figure><img src="/files/7DnjDorQ3iWiSf4X7kVa" alt=""><figcaption></figcaption></figure>

This will create your Consumer Key and Consumer Secret. Copy both the Key and Secret and paste them into the EdgeTag form.

<figure><img src="/files/zYYqqAqHJzJiIDY2JsVf" alt=""><figcaption></figcaption></figure>


# API

Below you will find the keys to generate secret payloads for adding or updating a channel via the [Script API](/api/api/script).

### WOO\_COMMERCE\_SHOP\_NAME

In this secret, you would store the store ID associated with the CRM.\
\
**Example value**\
example.com\
\
**Type**\
IDENTIFIER\
\
**Optional**\
false

### WOO\_COMMERCE\_API\_SECRET

In this secret, you would store the Consumer secret that customers generate.\
\
**Example value**\
cs\_c86aa5336c4c97426bfc98362e324b37a7e84653\
\
**Type**\
PASSWORD\
\
**Optional**\
false

### WOO\_COMMERCE\_API\_KEY

In this secret, you would store the Consumer key that customers generate.\
\
**Example value**\
ck\_c273275f6c7c2a334720be8b121a8b8b2b2f5f9d\
\
**Type**\
PASSWORD\
\
**Optional**\
false

### WOO\_COMMERCE\_BACK\_FILL

In this secret, you would store whether the customer wants to enable backfill or not.\
\
**Possible values**\
1 or 0

**Type**\
CHECKBOX

**Optional**\
true


# BigCommerce

A CRM connection is crucial for maintaining the quality and delivery of signals. It serves two main purposes:\
\
1\. **Customer Activity Tracking**: We analyze past orders to determine whether a customer is a first-time buyer or a repeat customer.\
&#x20;  \
2\. **Backup Mechanism**: In case of a failure in the browser, we can directly retrieve purchase data from Shopify, allowing us to link attribution to EdgeTag data effectively.

{% hint style="warning" %}
For the NC/RC feature to work, this step is necessary.
{% endhint %}


# Onboarding

Connecting BigCommerce CRM is straightforward; we need your store name and access token, and we'll handle the rest. After you obtain all the information required for the form, paste it into the form and click Save & Deploy.

<figure><img src="/files/8onNACFHjJUmD6YtTQuN" alt=""><figcaption></figcaption></figure>

### Store Hash

You can get Store Hash from the URL of your BigCommerce website.\
\
If your BigCommerce website URL is: <https://store-cx3if5k23y.mybigcommerce.com/>, your Store Hash is `cx3if5k23y`.

### Access Token

To obtain an access token, go to your BigCommerce admin, select Settings, then API, and click on Store-level API accounts.

{% hint style="warning" %}
A maximum of 50 store-level accounts can be created per store.
{% endhint %}

<figure><img src="/files/wTFGNwoz5Sk9ud7PkRqQ" alt=""><figcaption></figcaption></figure>

Click on Create API Account.

<figure><img src="/files/W9s3b8Na1IAU8o3KHWlu" alt=""><figcaption></figcaption></figure>

Under the Token type drop-down menu, select **V2/V3 API token**.

<figure><img src="/files/SfJnJQbn9aV3vxBPqEEJ" alt=""><figcaption></figcaption></figure>

Please provide a name for the account. For example: EdgeTag.

<figure><img src="/files/VfNkwYZVPPl8R9uywbzy" alt=""><figcaption></figcaption></figure>

Next, locate the various OAuth scopes and select the read-only option in the Orders section. Once you have made your changes, click on Save.

<figure><img src="/files/iS5KbJtN6xZCzQSnuhtc" alt=""><figcaption></figcaption></figure>

A successful save will open a pop-up window showing the API credentials. Copy the Access Token to your clipboard and paste it into the Access Token field.

<figure><img src="/files/C8taFd1MezoE6zPbDQ4Q" alt=""><figcaption></figcaption></figure>


# API

Below you will find the keys to generate secret payloads for adding or updating a channel via the [Script API](/api/api/script).

### BIG\_COMMERCE\_STORE\_HASH

In this secret, you would store the store hash associated with the CRM.\
\
**Example value**\
cx3if5k23y\
\
**Type**\
IDENTIFIER\
\
**Optional**\
false

### BIG\_COMMERCE\_ACCESS\_TOKEN

In this secret, you would store the access token that customers generate.\
\
**Example value**\
239az9h06mecbaelcejzx3by13jode5\
\
**Type**\
PASSWORD\
\
**Optional**\
false


# Salesforce Commerce Cloud

A CRM connection is crucial for maintaining the quality and delivery of signals. It serves two main purposes:\
\
1\. **Customer Activity Tracking**: We analyze past orders to determine whether a customer is a first-time buyer or a repeat customer.\
&#x20;  \
2\. **Backup Mechanism**: In case of a failure in the browser, we can directly retrieve purchase data from Shopify, allowing us to link attribution to EdgeTag data effectively.

{% hint style="warning" %}
For the NC/RC feature to work, this step is necessary.
{% endhint %}


# Onboarding

Connecting Salesforce CC CRM is straightforward; we need your store name and access token, and we'll handle the rest. After you obtain all the information required for the form, paste it into the form and click Save & Deploy.

<figure><img src="/files/cwaCyIYCg6cp3RiXixQK" alt=""><figcaption></figcaption></figure>


# API

Below you will find the keys to generate secret payloads for adding or updating a channel via the [Script API](/api/api/script).

### SALESFORCE\_SHOP\_NAME

In this secret, you would store the store name associated with the CRM.\
\
**Example value**\
**example.com**\
\
**Type**\
IDENTIFIER\
\
**Optional**\
false

### SALESFORCE\_SITE\_ID

In this secret, you would store the store ID associated with the CRM.\
\
**Example value**\
RefArch\
\
**Type**\
TEXT\
\
**Optional**\
false

### SALESFORCE\_CLIENT\_ID

In this secret, you would store the client ID associated with the CRM.\
\
**Example value**\
c0c6fe7c-6b41-42e2-9291-ce33f805a07b\
\
**Type**\
TEXT\
\
**Optional**\
false

### SALESFORCE\_CLIENT\_SECRET

In this secret, you would store the client secret that customers generate.\
\
**Example value**\
art4afw4taXeyewrfMr\
\
**Type**\
PASSWORD\
\
**Optional**\
false


# Overview

EdgeTag ships with dozens of pre-built channels (Meta CAPI, Google Ads, Klaviyo, TikTok, and many more) that cover the destinations most customers need. But sooner or later, you run into something that isn't in the catalog — an in-house analytics tool you want to forward events to, a bespoke identity stitching rule, a per-channel payload tweak, a first-party API endpoint you want to expose on your own domain, or a nightly export to your warehouse.

Playground is for exactly those cases. It lets you write the same kind of code our built-in channels run — on the EdgeTag edge (Cloudflare Workers) and in the browser — without leaving the UI. An AI agent turns natural-language prompts into code, a simulation runtime replays sample events through your code before you deploy, and saves your changes, publishing them instantly to your own EdgeTag infrastructure.

{% embed url="<https://www.loom.com/share/a745bc0e27a348bc8644fc6eb03d20f9>" %}

### Two options

Playground has two options. Pick based on what you're trying to do.

{% columns %}
{% column %}
**Destination** — build a new integration from scratch. Send events to a third-party API, expose a custom first-party endpoint on your domain, persist data in your own D1 database, and run a cron job. Destination gives you HTTP requests, identity-graph access, infrastructure bindings, and scheduled tasks.

{% content-ref url="/pages/a22b22dc8123e5cc91be423b8232290063eb9190" %}
[Destination](/playground/destination)
{% endcontent-ref %}
{% endcolumn %}

{% column %}
**Transformation** — reshape the events already flowing through your existing channels. Drop events you don't want to forward, enrich payloads with extra fields, rename events conditionally, or fan out additional events. Transformations run as a plugin before every channel and are pure event transforms — no HTTP, no storage.

{% content-ref url="/pages/199d549a34fa566156bf3035540ff0cb79382594" %}
[Transformation](/playground/transformation)
{% endcontent-ref %}
{% endcolumn %}
{% endcolumns %}

{% hint style="info" %}
Rule of thumb: if you're creating a **new** endpoint or integration, use a Destination. If you're modifying events going to **existing** channels, use a Transformation.
{% endhint %}

### What you can build

A few things customers have built with Playground:

* A custom pixel for an in-house attribution tool
* A warehouse webhook that mirrors every Purchase into BigQuery via a forwarding service
* Cross-domain ID stitching that reads a cookie on one domain and writes it on another
* A SHA-256 PII hashing policy is applied uniformly across every outgoing event
* Dropping `PageView` events from going to paid-media channels, while keeping them in analytics
* Conditional event fan-out (e.g., emit `Purchase_Facebook` when the user's last paid-media click was on Facebook)
* A first-party `/api/subscribe` endpoint backed by D1
* A nightly export that dumps yesterday's events to R2 as CSV

### How it works

Playground is a chat-based code environment. The typical flow:

{% stepper %}
{% step %}

### Describe what you want

Tell the agent in plain English what the integration should do. It generates the code for the relevant files and explains the choices it made.
{% endstep %}

{% step %}

### Edit in the code editor

Every file is editable directly; the agent's output is a starting point, not a lock-in.
{% endstep %}

{% step %}

### Simulate

Pick a sample event (`Purchase`, `PageView`, `AddToCart`) and run your code in an isolated sandbox. Every HTTP request, log line, and return value is captured so you can verify the behavior before anything reaches production.
{% endstep %}

{% step %}

### Save

Once you're happy, saving deploys your code to your EdgeTag edge. There is no separate publish step.
{% endstep %}
{% endstepper %}

<figure><img src="/files/tFoEo5lC6ftWNWPY8Ltv" alt="Playground chat, code editor, and simulation output"><figcaption></figcaption></figure>


# Destination

A Destination is custom code that behaves like one of our built-in channels. It runs at event time on the EdgeTag edge, at page load in the browser, and optionally via a cron. It can call third-party APIs, read and write your identity graph, store data in its own database, and expose HTTP endpoints on your domain.

Use a Destination when you need something that isn't covered by the channels EdgeTag ships with. Typical examples include forwarding events to an in-house analytics tool, building a warehouse webhook, or exposing a first-party `/api/subscribe` endpoint backed by your own database.

{% hint style="info" %}
If you're modifying events flowing through **existing** channels (dropping, renaming, enriching), use a [Transformation](broken://pages/SbZJG1KnDnq3GEIyi6Vc) instead. Destinations are for creating **new** integrations.
{% endhint %}

{% embed url="<https://www.loom.com/share/9ddb51e5014644d5b2ea9e9f7115dd41>" %}

### What's in a Destination

A Destination is made up of a small set of stage files and, optionally, custom HTTP endpoints:

* **Edge files** — `edge/init`, `edge/tag`, `edge/user`, `edge/scheduled`. Run on the EdgeTag edge (Cloudflare Workers) at their respective lifecycle points.
* **Browser files** — `browser/init`, `browser/tag`, `browser/user`. Run in the visitor's browser alongside the EdgeTag SDK.
* **CDN APIs** — browser-callable HTTP endpoints on your EdgeTag domain.
* **Server APIs** — authenticated server-only HTTP endpoints, with access to the current user.

Only the files you actually write are invoked — leaving a file empty skips that stage entirely.

{% content-ref url="/pages/983b45d97791714f87a341bc60152bcdf2082402" %}
[File Structure](/playground/destination/file-structure)
{% endcontent-ref %}

### Capabilities at a glance

From Destination code, you can:

* Make outbound HTTP requests through `params.requestHandler`
* Read and write the EdgeTag identity graph via `params.userSave`, `params.userGet`, and `params.providerSave`
* Persist state in a **D1** database, **KV** store, **R2** bucket, or **Analytics Engine** dataset
* Run **scheduled** jobs on a cron
* Load and initialize third-party scripts in the browser, with full consent awareness
* Expose **custom HTTP endpoints** (authenticated server APIs and browser-callable CDN APIs) on your EdgeTag domain


# File Structure

A Destination is organised into a fixed set of stage files, plus any custom HTTP endpoints you add. Every file is optional — only the ones you fill in are invoked.

{% code title="Destination layout" %}

```
playground/
├── edge/
│   ├── init         runs on the CDN edge when a visitor session starts
│   ├── tag          runs on the CDN edge for every tracked event
│   ├── user         runs on the CDN edge when user identity changes
│   └── scheduled    runs on the CDN edge on a cron schedule
├── browser/
│   ├── init         runs in the browser once on page load
│   ├── tag          runs in the browser for every tracked event
│   └── user         runs in the browser when user identity changes
└── apis/
    ├── cdn/         browser-callable HTTP endpoints
    └── server/      authenticated server-only HTTP endpoints
```

{% endcode %}

### Edge files

Edge files run on the EdgeTag CDN edge (Cloudflare Workers). They see the full request context, have access to your variables and infrastructure bindings, and can make outbound HTTP requests.

* **`edge/init`** — runs once when a visitor session starts. Use it to capture click IDs from URL parameters, detect new users, or read from the identity graph before the first event fires.
* **`edge/tag`** — runs for every tracked event (`Purchase`, `PageView`, `AddToCart`, `ViewContent`, `InitiateCheckout`, etc., and any custom events you send). This is where most Destination logic lives: forwarding events to third-party APIs, enriching payloads with server-only data, and hashing PII.
* **`edge/user`** — runs when user identity explicitly changes (login, signup, profile update). Use it to sync identity to a CRM or resolve the user against an external system.
* **`edge/scheduled`** — runs periodically on a cron schedule. Use it for batch exports, periodic syncs, or cleanup jobs.

### Browser files

Browser files run in the visitor's browser and are loaded by the EdgeTag SDK. They're the right place for anything that needs to touch the page — loading third-party scripts, calling a pixel's browser SDK, or reading values from the DOM.

* **`browser/init`** — runs once on page load. Use it to load third-party scripts (like `fbq`, `gtag`, `pintrk`), gated by consent.
* **`browser/tag`** — runs for every tracked event. Use it to call the third-party pixel's tracking API with the event data.
* **`browser/user`** — runs when the user identity changes in the browser. Use it to update the third-party pixel with user data.

{% hint style="warning" %}
Browser code runs directly on your website. Errors in this code can break site functionality. Test thoroughly before deploying.
{% endhint %}

### API endpoints

In addition to the stage files, a Destination can expose its own HTTP endpoints on your EdgeTag domain. You add them from the Playground UI, one file per endpoint.

* **CDN APIs** — browser-callable endpoints (no authentication). Use these for first-party endpoints your site or app needs to hit directly — things like `/api/subscribe` or `/api/consent-preferences`.
* **Server APIs** — authenticated endpoints that also receive `params.currentUser`. Use these for admin-only or server-to-server integrations.

Both endpoint types have access to your variables and infrastructure bindings — but not to `requestHandler` or identity-graph helpers.

### Enabled stages

Only the files you fill in are invoked. An empty `edge/scheduled`, for example, means no cron is registered at all; an empty `browser/init` means no browser bootstrapping runs. You can start with a single file (usually `edge/tag`) and add more as you need them.

{% hint style="info" %}
When you save, Playground wraps your edge code into a ServiceWorker module and your browser code into an ES module. You don't write the wrappers yourself — just the function bodies.
{% endhint %}


# Docs

The **Docs** tab is your live reference for whatever file you're working on. Select a file in the navigator on the left, and the Docs tab shows you exactly what that file receives, what helpers it can call, which Variables and Infrastructure bindings are currently available to it, and a complete example tailored to that file.

It's dynamic: add a Variable in the **Variables** tab or a binding in the **Infrastructure** tab, and the Docs tab immediately lists it by name and shows how to use it in the file you're viewing. That way you never have to leave the UI to look up a param shape or guess a binding name.

### What each file's reference contains

Every file (edge, browser, or API endpoint) has the same sections in the Docs tab:

* **When it runs** — a one-sentence description of the trigger for this file (page init, every event, user identity change, cron, browser load, etc.) and what it's typically used for.
* **Input (params)** — the full TypeScript shape of the `params` object this file receives. Includes every field, its type, and a short inline comment.
* **Helpers** — one block per helper available in this file, each with a usage signature and a one-line description. For edge files that's `requestHandler`, `userSave`, `providerSave`, `userGet`, `logger`; for `edge/scheduled` it adds the `reporting.*` and `userKey.*` helpers; for API handlers it's the `Response` return shape.
* **Variables** — the list of Variables currently configured, each shown as `params.secrets.NAME`. When nothing is configured you see a placeholder telling you to add Variables in the Variables tab.
* **Infrastructure Bindings** — the list of bindings currently configured, each shown as `params.infra.BINDING_NAME`. When nothing is configured you see a placeholder pointing at the Infrastructure tab.
* **Example** — a complete, copy-pasteable example that uses the params, helpers, and any Variables/bindings you've configured to show a realistic usage for this file.

### Dynamic sections

The **Variables** and **Infrastructure Bindings** sections reflect your current configuration in real time:

* Add a Variable named `API_KEY` in the [Variables](/playground/destination/variables) tab → the Docs panel for every edge file now shows `params.secrets.API_KEY` under Variables, and the Example updates to reference it.
* Add a D1 binding named `ORDERS_DB` in the [Infrastructure](/playground/destination/infrastructure) tab → the Docs panel now lists `params.infra.ORDERS_DB` and the Example shows a query against it.
* Mark a Variable as client-side → it now also appears in the Docs for browser files, under the correct access path (`params.manifest.variables.NAME` for `browser/init`, `params.manifestVariables.NAME` for `browser/tag` and `browser/user`).

This means you rarely need to guess: the name you see in Docs is the name you use in code.

### Switching between files

The file navigator on the left lists every file in the Destination: the edge files (`edge/init`, `edge/tag`, `edge/user`, `edge/scheduled`), browser files (`browser/init`, `browser/tag`, `browser/user`), and any CDN or Server API endpoints you've added. Clicking a file updates the Docs tab to that file's reference.

{% hint style="info" %}
Use Docs alongside the [Code](/playground/destination/code) tab while you're writing — Code shows your code, Docs shows the contract. They're designed to be read side by side.
{% endhint %}


# Code

Every stage file in a Destination is an async function that receives a `params` object. This page is the reference for what's in `params` for each file and what the function should do with it.

All files are optional: leave a file empty and that stage is skipped. You never write wrappers, imports, or exports — only the function body.

## `edge/tag`

Runs on the CDN edge for every tracked event. This is where most Destination logic lives.

Available on `params`:

* `payload` — the event: `eventName`, `eventId`, `pageUrl`, `pageTitle`, `referrer`, `locale`, `data` (the event's standard fields, e.g. `currency`, `value`, `orderId`, `contents`).
* `userId` — the EdgeTag user ID for this visitor.
* `user` — hashable PII (`email`, `phone`, `firstName`, `lastName`, `city`, `state`, `zip`, `country`, …), if available.
* `customData` — any custom tags you attached via the SDK.
* `providerData` — click IDs and cookies captured per provider (e.g. `fbp`, `fbc`, `gclid`).
* `hostData` — request metadata (`userAgent`, `ip`, `country`, `city`, `region`, `timezone`, `postalCode`).
* `origin`, `domain`, `platform` — where the event came from.
* `secrets` — your [Variables](/playground/destination/variables) as a plain object.
* `infra` — your [Infrastructure](/playground/destination/infrastructure) bindings (D1, KV, R2, Analytics Engine).
* `requestHandler(url, config)` — make outbound HTTP requests.
* `userSave`, `userGet`, `providerSave` — read and write the EdgeTag identity graph.
* `logger.log(...)`, `logger.error(...)` — visible in Simulation output.

{% code title="edge/tag — forward Purchase to a third-party API with SHA-256-hashed email" %}

```javascript
if (params.payload.eventName !== 'Purchase') {
  return
}

const email = params.user?.email?.toLowerCase().trim()
let hashedEmail
if (email) {
  const bytes = await crypto.subtle.digest(
    'SHA-256',
    new TextEncoder().encode(email)
  )
  hashedEmail = Array.from(new Uint8Array(bytes))
    .map((b) => b.toString(16).padStart(2, '0'))
    .join('')
}

await params.requestHandler('https://api.example.com/conversions', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${params.secrets.API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    orderId: params.payload.data.orderId,
    value: params.payload.data.value,
    currency: params.payload.data.currency,
    hashedEmail,
    country: params.hostData.country,
  }),
})

params.logger.log('Forwarded Purchase', params.payload.data.orderId)
```

{% endcode %}

## `edge/init`

Runs once the browser SDK loads and invokes it. Use it to capture click IDs from URL parameters or cookies, detect new users, and read or write the identity graph before the first event fires.

Available on `params`:

* `userId`, `isNewUser`
* `session` — `{ isNewSession, sessionId }` or `null`
* `cookies` — raw cookie string from the request
* `hostData`, `secrets`, `infra`
* `requestHandler`, `userSave`, `userGet`, `providerSave`, `logger`

```javascript
if (params.isNewUser) {
  params.logger.log('New user detected', params.userId)
}
```

## `edge/user`

Runs on the edge when user identity is sent (login, signup, profile update). Use it to sync identity to a CRM or resolve the user against an external system.

Available on `params`:

* `payload` — the user fields being saved
* `userId`, `user`, `customData`
* `hostData`, `secrets`, `infra`
* `requestHandler`, `userSave`, `userGet`, `providerSave`, `logger`

```javascript
await params.requestHandler('https://crm.example.com/identify', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${params.secrets.CRM_TOKEN}` },
  body: JSON.stringify({ userId: params.userId, ...params.payload }),
})
```

## `edge/scheduled`

Runs periodically on a cron schedule (every 5 minutes). Use it for batch exports, periodic syncs, or cleanup jobs.

Available on `params`:

* `scheduledTime` — a `Date` for the scheduled run
* `secrets`, `infra`, `logger`, `requestHandler`
* `reporting.saveInDatabase(sql, bindings)` — insert a row
* `reporting.saveBatchInDatabase(sql, bindingsArray)` — batch insert
* `reporting.getFromDatabase(sql, bindings)` — read
* `userKey.byRouteKey(key)` — look up users by a routing key (email or user Id)

```javascript
const since = new Date(params.scheduledTime.getTime() - 24 * 60 * 60 * 1000)
const rows = await params.reporting.getFromDatabase(
  'SELECT order_id, value FROM orders WHERE created_at > ?',
  [since.toISOString()]
)

params.logger.log(`Exporting ${rows?.results.length ?? 0} rows`)
```

## `browser/init`

Runs once in the browser on page load. Use it to load third-party scripts and initialize pixels, gated by consent.

Available on `params`:

* `userId`, `isNewUser`, `session`
* `baseUrl` — EdgeTag CDN base URL
* `manifest.variables` — your client-side [Variables](/playground/destination/variables)
* `manifest.package`, `manifest.tagName`, `manifest.geoRegions`
* `keyName`, `destination`
* `consentData.consent`, `consentData.categories`, `consentData.consentSettings`
* `sendTag(event)` — forward an event to your own `browser/tag`
* `sendEdgeData(data, providers, options?)` — persist data through the edge
* `getEdgeData(keys, callback)` — read previously saved edge data
* `executionContext` — a `Map` shared across calls in the same page load

```javascript
if (!params.consentData.categories.advertising) return

const script = document.createElement('script')
script.src = `https://cdn.example.com/pixel.js?id=${params.manifest.variables.PIXEL_ID}`
script.async = true
document.head.appendChild(script)
```

## `browser/tag`

Runs in the browser for every tracked event. Use it to call the third-party pixel's tracking API with the event data.

Available on `params`:

* `userId`, `sessionId`, `eventId`, `eventName`, `data`
* `manifestVariables` — your client-side [Variables](/playground/destination/variables)
* `destination`
* `sendTag`, `getEdgeData`, `executionContext`

```javascript
if (window.fbq) {
  window.fbq('track', params.eventName, {
    value: params.data.value,
    currency: params.data.currency,
  })
}
```

## `browser/user`

Runs in the browser when the user identity changes. Use it to update a third-party pixel with the latest user data.

Available on `params`:

* `userId`, `data`, `manifestVariables`, `destination`

## CDN API handlers

Each CDN API endpoint is a handler function. CDN APIs are browser-callable — no authentication — and are the right place for first-party endpoints your site needs to hit directly (for example `/api/subscribe` or `/api/consent-preferences`).

Available on `params`:

* `endpoint` — the endpoint name
* `method` — `GET`, `POST`, `PUT`, or `DELETE`
* `request` — the standard `Request` object
* `secrets`, `infra`, `logger`

Return a standard `Response`.

```javascript
const body = await params.request.json()

await params.infra.SUBSCRIBERS_DB
  .prepare('INSERT INTO subscribers (email, created_at) VALUES (?, ?)')
  .bind(body.email, new Date().toISOString())
  .run()

return new Response(JSON.stringify({ status: 'ok' }), {
  headers: { 'Content-Type': 'application/json' },
})
```

## Server API handlers

Server APIs are EdgeTag backend authenticated server-only endpoints. They receive everything a CDN API does, plus `params.currentUser` with the authenticated user (`userId`, `email`, `role`, `teamId`, …). Use these for admin-only or server-to-server integrations.

## Available web APIs

Inside edge files, you have the Workers runtime, which includes:

* `crypto.subtle.digest(...)`, `crypto.randomUUID()`
* `URL`, `URLSearchParams`
* `TextEncoder`, `TextDecoder`
* `atob`, `btoa`
* `structuredClone`

Browser files have the full browser API surface (`window`, `document`, `fetch`, `localStorage`, `navigator`, and so on).

## Return values

Most stage files don't need to return anything — they're fire-and-forget.

* `edge/*` and `browser/*` — returning nothing is fine. Browser files can return `{ skipEvent: true }` to stop EdgeTag from continuing with this event.
* **CDN APIs and Server APIs** — must return a `Response`.

{% hint style="info" %}
Throwing an exception in a stage file doesn't break the event pipeline for other channels. The error is captured and surfaced in [Simulation](/playground/destination/simulation) output and your EdgeTag logs.
{% endhint %}


# Infrastructure

Some Destinations need a persistent state — a deduplication window, a rollup counter, a nightly file export, and a first-party API endpoint backed by a database. The **Infrastructure** tab lets you attach Cloudflare storage services to your Destination so your code can read and write them directly.

### Available services

| Service | Good for                                                    |
| ------- | ----------------------------------------------------------- |
| **D1**  | SQL database — reporting, subscriber lists, relational data |
| **KV**  | Key-value store — fast reads, session caches, deduplication |
| **R2**  | Object storage — file exports, archives, large blobs        |

Each service you add gets a **binding name** (for example `MY_DB`, `CACHE`, `EXPORTS`). Bindings are exposed as `params.infra.<BINDING_NAME>` inside every edge file and every API handler.

You can also open the Visualization tab and see exactly how your infrastructure works.

<figure><img src="/files/hZSOXFTV1G4uZ96OeIiP" alt=""><figcaption></figcaption></figure>

### Using bindings in code

**D1 (SQL database)**

```javascript
const row = await params.infra.MY_DB
  .prepare('SELECT * FROM subscribers WHERE email = ?')
  .bind(email)
  .first()
```

**KV (key-value store)**

```javascript
await params.infra.CACHE.put(`dedupe:${orderId}`, '1', { expirationTtl: 86400 })
const seen = await params.infra.CACHE.get(`dedupe:${orderId}`)
```

**R2 (object storage)**

```javascript
await params.infra.EXPORTS.put(
  `orders/${date}.csv`,
  csvString,
  { httpMetadata: { contentType: 'text/csv' } }
)
```

### Simulation and infrastructure

In [Simulation](/playground/destination/simulation), every binding is mocked in memory. D1 queries are captured but not executed, KV and R2 operations use an in-memory `Map`, and Analytics Engine writes are logged. Nothing persists between simulation runs, and no data reaches production.

{% hint style="info" %}
Add bindings from the Infrastructure tab before you reference them in code. Variables come from the [Variables](/playground/destination/variables) tab, not Infrastructure.
{% endhint %}


# Simulation

Simulation runs your Destination code in an isolated sandbox against a realistic sample event, captures every side effect, and shows you the result — without anything reaching production. Use it after every change before you save.

<figure><img src="/files/o1Q9ynmoIIywMEwLWumL" alt=""><figcaption></figcaption></figure>

### What gets run

When you click **Simulate**, Playground:

1. Takes the current code from every stage file
2. Injects your [Variables](/playground/destination/variables) as `params.secrets`
3. Mocks every [Infrastructure](/playground/destination/infrastructure) binding in memory
4. Runs the file you selected (default is `edge/tag`) against the current sample event
5. Captures all HTTP requests, logs, and any thrown errors

Nothing leaves the sandbox. No real API calls are made, no rows are written to your D1 database, no files are uploaded to R2.

### Sample events

Every session starts with three pre-populated sample events you can switch between:

* **Purchase** — a full purchase with two line items, a value, an order ID, and user PII.
* **PageView** — a page view with a name and category.
* **AddToCart** — a single-item add-to-cart with a value and currency.

You change the sample event from the UI (click on View/Edit Input); the rest of the `params` object (user, host data, provider data, custom data) stays the same across events.

### What you see

The simulation output has three sections:

* **Captured requests** — every call your code made through `params.requestHandler`. Shows URL, method, headers, and body for each request.
* **Logs** — everything you wrote with `params.logger.log(...)` or `params.logger.error(...)`.
* **Error** — if your code threw, the error message and stack.

Sensitive values are redacted automatically: any header or body key matching `authorization`, `token`, `api-key`, `api_key`, `secret`, `password`, or `cookie` is replaced with `[REDACTED]` before it's shown.

### A complete worked example

Say you're writing a Destination that forwards `Purchase` events to a third-party conversions API with a SHA-256-hashed email and a Bearer token from Variables. After you simulate against the Purchase sample event, the output will look roughly like this:

{% code title="Simulation output" %}

```json
{
  "requests": [
    {
      "url": "https://api.example.com/conversions",
      "method": "POST",
      "headers": {
        "Authorization": "[REDACTED]",
        "Content-Type": "application/json"
      },
      "body": {
        "orderId": "ORD-2024-001234",
        "value": 149.99,
        "currency": "USD",
        "hashedEmail": "a1b2c3...",
        "country": "US"
      }
    }
  ],
  "logs": [
    "Forwarded Purchase ORD-2024-001234"
  ]
}
```

{% endcode %}

From that output, you can verify your code is:

* Hitting the right URL with the right method
* Passing the right headers (your Bearer token is present, even though it's redacted in the display)
* Sending the right body fields, in the right shape
* Logging what you expected

If anything is off — wrong URL, missing field, a `null` value you didn't expect — you fix it in the editor and simulate again. No deploy, no rollback, no risk.

{% hint style="info" %}
Simulate after every meaningful change. It's the fastest way to catch issues before they hit production, and it's the only way to see exactly what your third-party APIs will receive.
{% endhint %}


# Variables

Variables are key/value pairs injected into your Destination code at runtime. Use them for anything you don't want hard-coded — API keys, pixel IDs, endpoint URLs, feature flags, environment-specific values.

You manage variables from the **Variables** tab in the Playground UI. Each variable has a name, a value, and a flag that controls whether it's available in the browser.

You have two types. **Variables** and **Secrets.**

Use **Variables** when you need to store non-sensitive information, like URLs or pixel IDs.  Use **Secrets** when you need to store sensitive information, such as access tokens or signing keys.

### Server-side access

All variables are available in every edge file (`edge/init`, `edge/tag`, `edge/user`, `edge/scheduled`) and in your CDN and Server API handlers, under `params.secrets`:

```javascript
const apiKey = params.secrets.API_KEY
const endpoint = params.secrets.WEBHOOK_URL
```

Variable names are whatever you type in the UI — they aren't transformed.

### Exposing a variable to the browser

By default, variables stay server-side. If you need a variable in browser code (for example, a public pixel ID), tick the **"also include on client"** checkbox when you create it.

Client-side variables are available under a slightly different path depending on the browser file:

* **`browser/init`** — `params.manifest.variables.PIXEL_ID`
* **`browser/tag`** — `params.manifestVariables.PIXEL_ID`
* **`browser/user`** — `params.manifestVariables.PIXEL_ID`

```javascript
// browser/tag
if (window.fbq) {
  window.fbq('init', params.manifestVariables.PIXEL_ID)
  window.fbq('track', params.eventName, { value: params.data.value })
}
```

{% hint style="warning" %}
Anything you expose to the client is shipped to every visitor's browser and is effectively public. Only mark variables as client-side when they're safe to expose — public pixel IDs, feature flags, or public API keys. Never mark private API keys or signing secrets as client-side.
{% endhint %}

### Redaction in Simulation

When you run a simulation, Playground redacts sensitive values from the captured output. Any header or body key matching `authorization`, `token`, `api-key`, `api_key`, `secret`, `password`, or `cookie` is replaced with `[REDACTED]` before being shown. This keeps your logs clean even when your code legitimately forwards secrets to third-party APIs.


# Transformation

A Transformation is an engine that sits in front of every channel/destination. It sees every event you send to EdgeTag and can reshape it, drop it, or fan out additional events before the event reaches any individual channel.

Use a Transformation when you want to modify events already flowing through the channels EdgeTag ships with — for example, dropping `PageView` events from paid-media channels, enriching every Purchase with a server-side `source` field, hashing PII uniformly across channels, or emitting a `Purchase_Facebook` event when the user's last paid-media click was Facebook.

{% hint style="info" %}
A [Destination](/playground/destination) sends events to a new integration you're building from scratch. A Transformation reshapes events that are already going to existing channels. If you need to make outbound HTTP requests or store data, use a Destination.
{% endhint %}

{% embed url="<https://www.loom.com/share/edab0372d54442759faabb12f494809b>" %}

### The six execution levels

A Transformation consists of six files, one per execution level. Three run on the edge and three run in the browser, each at a different granularity:

| Level    | Edge file     | Browser file        | Runs                                                          |
| -------- | ------------- | ------------------- | ------------------------------------------------------------- |
| Root     | `tagRoot`     | `clientTagRoot`     | Once per event, before channel routing                        |
| Channel  | `tagChannel`  | `clientTagChannel`  | Once per provider channel (for example Facebook)              |
| Instance | `tagInstance` | `clientTagInstance` | Once per provider instance (for example `facebook\|\|pixel1`) |

The rule of thumb: use **root** for rules that apply to all channels, channel for rules tied to a specific provider, and instance for rules tied to a specific pixel or account within a provider.

To set channel or instance settings, exit the Playground from full screen and select options that you would like.

<figure><img src="/files/ajMCKH4fBPVHYfizkbpn" alt=""><figcaption></figcaption></figure>

### What a Transformation can do

Every file returns an object that tells EdgeTag how to handle the event. Any combination of these four is valid:

* **Modify the payload** — return `{ payload: <modified> }` to change the event before it reaches channels.
* **Skip the event** — return `{ skipEvent: true }` to drop the event for this level (everywhere, for this channel, or for this instance).
* **Emit additional events** — return `{ additionalEvents: [<event>, ...] }` to fan out extra events alongside the original.
* **Update user data** — return `{ user: <modified> }` to change the PII attached to this event.

Transformations are **pure event transforms** — they don't make HTTP requests, don't write to storage, and don't touch the identity graph. If you need any of that, use a Destination.


# File Structure

A Transformation is made up of six files — three that run on the edge and three that run in the browser. Each file is optional; only the ones you fill in are invoked.

{% code title="Transformation layout" %}

```
playground/
├── edge/
│   ├── tagRoot        runs on the CDN edge once per event
│   ├── tagChannel     runs on the CDN edge per provider
│   └── tagInstance    runs on the CDN edge per provider instance
└── browser/
    ├── clientTagRoot      runs in the browser once per event
    ├── clientTagChannel   runs in the browser per provider
    └── clientTagInstance  runs in the browser per provider instance
```

{% endcode %}

### Edge plugin levels

Edge files run on the EdgeTag CDN edge (Cloudflare Workers), before any channel receives the event. They have the full server-side context — PII on `params.user`, host data, paid-media attribution via `params.userKey`, and your [Secrets](/playground/transformation/secrets).

* **`tagRoot`** — runs **once per event**, before any channel routing. Use for rules that apply to every channel: drop an event everywhere, enrich every payload, and emit an extra event based on attribution.
* **`tagChannel`** — runs **once per provider channel**. Receives `params.providerId` (for example `facebook`, `klaviyo`, `tiktok`). Use for provider-specific rules: tweak the payload only when it's going to Facebook.
* **`tagInstance`** — runs **once per provider instance**, the most granular edge level. Also receives `params.providerId`. Use when you have multiple pixels or accounts inside a single provider and need different behavior for each.

### Browser plugin levels

Browser files run in the visitor's browser before the channel's own browser tag fires. They can skip a provider's browser execution, enrich the payload with browser-only data (like `window.innerWidth` or `document.referrer`), or shape data before it reaches the channel.

* **`clientTagRoot`** — runs **once per event** in the browser, before any provider tag. Use for browser-side global rules.
* **`clientTagChannel`** — runs **once per provider channel** in the browser. Receives `params.providerId`.
* **`clientTagInstance`** — runs **once per provider instance** in the browser. Also receives `params.providerId`.

### What each file receives

| File                | Side    | `providerId`? | Params shape                                                                                                  |
| ------------------- | ------- | :-----------: | ------------------------------------------------------------------------------------------------------------- |
| `tagRoot`           | edge    |       —       | `payload`, `user`, `variables`, `platform`, `userCustomData`, `hostData`, `logger`, `userKey`, `providerData` |
| `tagChannel`        | edge    |       ✓       | same as `tagRoot`, plus `providerId`                                                                          |
| `tagInstance`       | edge    |       ✓       | same as `tagChannel`                                                                                          |
| `clientTagRoot`     | browser |       —       | `payload`, `user`, `settings`, `variables`                                                                    |
| `clientTagChannel`  | browser |       ✓       | same as `clientTagRoot`, plus `providerId`                                                                    |
| `clientTagInstance` | browser |       ✓       | same as `clientTagChannel`                                                                                    |

See [Code](/playground/transformation/code) for the full reference.

### Choosing the right level

Start from what you're trying to do:

* **A rule that applies to every channel** → `tagRoot` (or `clientTagRoot` for browser). You usually only need one root file.
* **A rule tied to a specific provider** → `tagChannel`. Check `params.providerId`.
* **A rule tied to a specific pixel/account inside a provider** → `tagInstance`.

It's common to have only one of these files filled in. Fan out across multiple levels only when you genuinely need different rules at different granularities.

### Enabled stages

Empty files are skipped entirely — they aren't registered and don't add latency. You can ship a Transformation with only `tagRoot` filled in, and add other levels later as your rules grow.

{% hint style="info" %}
Playground wraps your plugin code into a registered plugin object on save. You don't write wrappers, imports, or exports — only the function bodies.
{% endhint %}


# Docs

The **Docs** tab is your live reference for whatever transformation file you're working on. Select a file in the navigator on the left, and the Docs tab shows you exactly what that file receives on `params`, what it can return, which [Secrets](/playground/transformation/secrets) are currently available to it (scoped to edge or browser), and a complete example tailored to that file.

It's dynamic: add a Secret in the Secrets tab, and the Docs tab immediately lists it by name under the right scope and updates the Example to reference it. You never have to leave the UI to look up a param shape, a return field, or a Secret name.

### What each file's reference contains

Every  file (`tagRoot`, `tagChannel`, `tagInstance`, `clientTagRoot`, `clientTagChannel`, `clientTagInstance`) has the same sections in the Docs tab:

* **When it runs** — where in the event pipeline this file fires: edge vs. browser, once per event vs. per provider vs. per provider instance.
* **Input (params)** — the full TypeScript shape of the `params` object this file receives (`payload`, `user`, `variables`, `platform`, `userCustomData`, `hostData`, `logger`, `userKey`, `providerData`; plus `providerId` for channel and instance levels, plus `settings` for browser files).
* **Return shape** — the fields you can return (`payload`, `user`, `additionalEvents`, `skipEvent`) and what each does for this specific level. Levels where `additionalEvents` isn't honored (anything that isn't a root file) say so explicitly.
* **Helpers** — for edge files, usage blocks for `params.userKey.getFirstClick()` / `getLastClick()` and `params.providerData`, including the literal-provider-ID return set and the click-ID-by-provider shape. For all files, `params.logger`.
* **Secrets** — the list of Secrets currently available to this file. Edge files show every Secret; browser files show only those with the client flag. Each is listed as `params.variables.NAME`. When nothing is configured, you see a placeholder pointing at the Secrets tab.
* **Example** — a complete, copy-pasteable example tailored to this file. The example uses `providerId` in channel/instance files, `settings` in browser files, and references your configured Secrets where relevant.

### Dynamic sections

The **Secrets** section reflects your current configuration in real time:

* Add a Secret named `API_KEY` → edge files now list `params.variables.API_KEY` under Secrets, and the Example updates to reference it.
* Mark that Secret as client-side → it also appears in the Secrets section for `clientTagRoot`, `clientTagChannel`, and `clientTagInstance`.
* Unmark the client flag → it disappears from the browser files' Docs but stays on the edge files.

The name you see in Docs is the name you use in code.

### Switching between files

The file navigator on the left lists every file. Clicking one updates the Docs tab to the reference for that file. Because the six levels share most of their `params` shape, a common workflow is:

1. Read the Docs for `tagRoot` to understand the base shape and helpers.
2. Switch to `tagChannel` to see what `providerId` adds.
3. Switch to `clientTagRoot` to see how browser params (`settings`, scoped `variables`) differ.

{% hint style="info" %}
Use Docs alongside the [Code](/playground/transformation/code) tab while you're writing — Code shows your code, Docs shows the contract. They're designed to be read side by side.
{% endhint %}


# Code

Every file in a Transformation is an async function that receives a `params` object and returns an object describing what EdgeTag should do with the event. This page is the reference: what's in `params` for each file, what you can return, and what each return shape actually does.

You never write wrappers, imports, or exports — only the function body.

### The return shape

Every file returns a plain object. Any combination of these fields is valid:

```javascript
return {}                                     // no-op (pass through)
return { payload }                            // modify the event
return { skipEvent: true }                    // drop the event
return { additionalEvents: [event1, ...] }    // fan out extra events
return { user }                               // update user data (edge only)
return { payload, additionalEvents }          // combine any of the above
```

Returning nothing at all is equivalent to `return {}`.

### `tagRoot`

Runs on the edge **once per event**, before any channel routing. Use for global rules — drop an event everywhere, enrich every payload, and emit an additional event based on attribution.

Available on `params`:

* `payload` — the event: `eventName`, `eventId`, `sdkVersion`, `locale`, `search`, `referrer`, `data` (standard event fields).
* `user` — hashable PII (`email`, `phone`, `firstName`, `lastName`, `zip`, …), or `null`.
* `variables` — your [Secrets](/playground/transformation/secrets) as a plain object.
* `configuration` — any configuration attached to the event.
* `platform` — e.g. `SHOPIFY`, `CUSTOM`.
* `userCustomData` — custom tags attached via the SDK.
* `hostData` — request metadata (`userAgent`, `ip`, `country`, `city`, `region`, `timezone`, …).
* `logger.log(...)`, `logger.error(...)`.
* `userKey.getFirstClick()`, `userKey.getLastClick()` — paid-media attribution helpers.
* `providerData` — raw click IDs and UTMs captured for this user.

```javascript
if (params.payload.eventName === 'Purchase') {
  const payload = structuredClone(params.payload)
  payload.data = { ...payload.data, source: 'website' }
  return { payload }
}
return {}
```

### `tagChannel`

Runs on the edge **once per provider channel**. Same params as `tagRoot`, plus:

* `providerId` — the channel being processed (e.g. `'facebook'`, `'google'`, `'tiktok'`).

Use for provider-specific rules.

```javascript
if (params.providerId === 'facebook') {
  const payload = structuredClone(params.payload)
  payload.data = { ...payload.data, fb_custom_field: 'my_value' }
  return { payload }
}
return {}
```

### `tagInstance`

Runs on the edge **once per provider instance** (for example `facebook||pixel1`), the most granular edge level. Same params as `tagChannel`, including `providerId`. Use when you have multiple pixels or accounts inside a provider and need different behavior for each.

### `clientTagRoot`

Runs in the browser **once per event**, before any provider tag fires.

Available on `params`:

* `payload` — `eventName`, `eventId`, `data`.
* `user` — reserved for browser user data (currently empty).
* `settings` — `userId`, `sessionId`, `geoCountry`, `geoRegion`, `isEURequest`, `ip`, `consent`, `consentCategories`, `userProperties`.
* `variables` — only the [Secrets](/playground/transformation/secrets) you've flagged as client-side.

```javascript
return {
  payload: {
    ...params.payload,
    data: { ...params.payload.data, screenWidth: window.innerWidth },
  },
}
```

### `clientTagChannel` / `clientTagInstance`

Same as `clientTagRoot`, plus `providerId`. Use these for browser-side provider- or instance-specific rules — for example, skipping a provider's browser pixel for EU traffic.

### Paid-media helpers

Two helpers on edge params make paid-media attribution easy. Both are easy to misuse, so read carefully.

**`params.userKey.getFirstClick()` and `getLastClick()`**

Returns a **provider ID** string or `null`. The provider ID tells you which paid channel drove this user; it is **not** a click ID, a URL, or a query string.

```javascript
const firstClickProvider = await params.userKey.getFirstClick()
const lastClickProvider = await params.userKey.getLastClick()

if (firstClickProvider === 'facebook') {
  // user's first paid-media touch was a Facebook click
}
if (lastClickProvider === null) {
  // organic/direct — no paid-media click on record
}
```

Valid return values are literal strings from a fixed set: `'facebook'`, `'googleAdsClicks'`, `'bing'`, `'snapchat'`, `'tiktok'`, `'twitter'`, `'linkedIn'`, `'pinterest'`, `'reddit'`, `'appLovin'`, `'taboola'`, `'outbrain'`, `'trybe'`, or `null`.

**`params.providerData`**

The raw click IDs and UTM values captured for this user, keyed by provider and then by the original query-parameter name:

```javascript
params.providerData.facebook?.fbclid          // Meta click ID, if present
params.providerData.googleAdsClicks?.gclid    // Google Ads click ID
params.providerData.utm?.utm_source           // 'google', 'newsletter', etc.
params.providerData.utm?.utm_medium
params.providerData.utm?.utm_campaign
```

An empty `providerData` means no paid-media clicks or UTMs were ever captured for this user.

{% hint style="warning" %}
`getFirstClick()` and `getLastClick()` return a provider ID like `'facebook'`, not the `fbclid`. Don't try to parse it with `new URL(...)` or `URLSearchParams` — just compare it to a literal provider ID or to `null`.
{% endhint %}

### What a Transformation can do

The four capabilities described at the top — modify, skip, fan out, update user — cover every real use case. Here's one real example of each.

{% stepper %}
{% step %}

### Modify a payload

Enrich every `Purchase` event with a server-side `source` field and a USD-converted value. Always use `structuredClone()` before mutating the payload so you never touch the input.

{% code title="tagRoot — enrich Purchase payloads" %}

```javascript
if (params.payload.eventName === 'Purchase') {
  const payload = structuredClone(params.payload)
  payload.data = {
    ...payload.data,
    source: 'website',
    valueUSD:
      payload.data.currency === 'USD'
        ? payload.data.value
        : payload.data.value * 1.08,
  }
  return { payload }
}
return {}
```

{% endcode %}
{% endstep %}

{% step %}

### Skip an event

Drop `PageView` events so they never reach any channel, and separately skip Facebook's browser pixel for visitors from the EU.

{% code title="tagRoot — drop PageView everywhere" %}

```javascript
if (params.payload.eventName === 'PageView') {
  return { skipEvent: true }
}
return {}
```

{% endcode %}

{% code title="clientTagChannel — skip Facebook in the browser for EU traffic" %}

```javascript
if (params.providerId === 'facebook' && params.settings.isEURequest) {
  return { skipEvent: true }
}
return {}
```

{% endcode %}
{% endstep %}

{% step %}

### Create new events

When a `Purchase` happens, and the user's first or last paid-media click was Facebook, fan out a second `Purchase_Facebook` event alongside the original. This is where `userKey` and `additionalEvents` work together.

{% code title="tagRoot — emit Purchase\_Facebook for Facebook-attributed purchases" %}

```javascript
if (params.payload.eventName === 'Purchase') {
  const firstClick = await params.userKey.getFirstClick()
  const lastClick = await params.userKey.getLastClick()
  if (firstClick === 'facebook' || lastClick === 'facebook') {
    const extra = structuredClone(params.payload)
    extra.eventName = 'Purchase_Facebook'
    return { additionalEvents: [extra] }
  }
}
return {}
```

{% endcode %}

{% hint style="info" %}
`additionalEvents` is honored from `tagRoot` on the edge and `clientTagRoot` in the browser. Each additional event then goes through channel routing like any other event.
{% endhint %}
{% endstep %}
{% endstepper %}

### Combining capabilities

A single return can do more than one thing. This example enriches the payload **and** emits an additional event for Facebook-attributed purchases in the same call:

```javascript
if (params.payload.eventName === 'Purchase') {
  const payload = structuredClone(params.payload)
  payload.data = { ...payload.data, source: 'website' }

  const lastClick = await params.userKey.getLastClick()
  if (lastClick === 'facebook') {
    const extra = structuredClone(payload)
    extra.eventName = 'Purchase_Facebook'
    return { payload, additionalEvents: [extra] }
  }

  return { payload }
}
return {}
```

### Return-value reference

| Field              | Type                   | Effect                                                           |
| ------------------ | ---------------------- | ---------------------------------------------------------------- |
| `payload`          | event object           | Replace the event payload for this level.                        |
| `user`             | user object or `null`  | Replace user data for this level. Edge only.                     |
| `additionalEvents` | array of event objects | Emit extra events. Root-level only (`tagRoot`, `clientTagRoot`). |
| `skipEvent`        | `boolean`              | `true` drops the event at this level.                            |

### What's not available

Transformations are pure event transforms — by design, they don't:

* make outbound HTTP requests (`requestHandler` is not available)
* write to the identity graph (`userSave`, `userGet`, `providerSave` are not available)
* access storage bindings (`infra` is not available)

If you need any of those, use a [Destination](/playground/destination).


# Secrets

Secrets are key/value pairs injected into your Transformation code at runtime. Use them for anything you don't want hard-coded — API keys, tokens, pixel IDs, feature flags. Secrets are stored encrypted and are never logged.

You manage secrets from the **Secrets** tab in the Playground UI. Each secret has a name, a value, and a flag that controls whether it's available in the browser.

### Edge vs. browser scope

Every secret is always available in edge plugin files (`tagRoot`, `tagChannel`, `tagInstance`). Only secrets with the **client** flag are available in browser plugin files (`clientTagRoot`, `clientTagChannel`, `clientTagInstance`).

The access path is the same on both sides: `params.variables.<NAME>`.

```javascript
// tagRoot (edge) — all secrets are available
const apiKey = params.variables.API_KEY      // ✓ defined
const pixelId = params.variables.PIXEL_ID    // ✓ defined

// clientTagRoot (browser) — only client-flagged secrets are available
const pixelId = params.variables.PIXEL_ID    // ✓ defined (if flagged)
const apiKey = params.variables.API_KEY      // ✗ undefined (edge-only)
```

{% hint style="warning" %}
Client-flagged secrets are shipped to every visitor's browser and are effectively public. Only flag values that are safe to expose — public pixel IDs, feature flags, public URLs. Never flag private API keys, signing secrets, or tokens as client-side.
{% endhint %}

### Adding a secret

1. Open the **Secrets** tab.
2. Enter a name and value.
3. Tick **client** if your browser plugin files need to read it.
4. Save.

Secret names are used verbatim — what you type in the UI is what you read in code.

### Redaction in Simulation

When you run a simulation, Playground redacts sensitive values from the captured output. Any header or body key matching `authorization`, `token`, `api-key`, `api_key`, `secret`, `password`, or `cookie` is replaced with `[REDACTED]` before being shown — even when your code legitimately passes a secret through to a third-party API.




---

[Next Page](/llms-full.txt/1)

