Meta Pixel Event Tracking on Umbraco | OpsBlu Docs

Meta Pixel Event Tracking on Umbraco

Track custom Meta Pixel events in Umbraco using Razor views, C# services, and JavaScript

Track custom Meta Pixel events in Umbraco to measure conversions, optimize ad campaigns, and build custom audiences. This guide covers client-side JavaScript tracking, server-side C# tracking with Conversions API, and Umbraco-specific event patterns.

Prerequisites

Before tracking custom events:

  • Meta Pixel Setup Complete - Follow Meta Pixel Setup Guide
  • Pixel ID Configured - Meta Pixel tracking code installed
  • Events Manager Access - Access to Facebook Events Manager
  • Development Environment - Visual Studio or VS Code

Standard Meta Events

Lead Event

Track lead generation forms:

@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage

<form id="contact-form" method="post">
    <input type="text" name="name" placeholder="Name" required />
    <input type="email" name="email" placeholder="Email" required />
    <input type="tel" name="phone" placeholder="Phone" />
    <textarea name="message" placeholder="Message"></textarea>
    <button type="submit">Submit</button>
</form>

<script>
    document.getElementById('contact-form').addEventListener('submit', function(e) {
        e.preventDefault();

        // Track Lead event
        fbq('track', 'Lead', {
            content_name: 'Contact Form',
            content_category: '@Model?.ContentType.Alias',
            value: 10.00,
            currency: 'USD'
        });

        // Submit form after tracking
        setTimeout(() => this.submit(), 100);
    });
</script>

Complete Registration Event

Track member registration:

@using Microsoft.AspNetCore.Mvc

@model YourProject.Models.RegisterModel

<form id="register-form" method="post">
    <input asp-for="Name" placeholder="Full Name" required />
    <input asp-for="Email" type="email" placeholder="Email" required />
    <input asp-for="Password" type="password" placeholder="Password" required />
    <button type="submit">Register</button>
</form>

<script>
    document.getElementById('register-form').addEventListener('submit', function(e) {
        e.preventDefault();

        // Track CompleteRegistration event
        fbq('track', 'CompleteRegistration', {
            content_name: 'Member Registration',
            status: 'completed',
            value: 25.00, // Estimated lifetime value
            currency: 'USD'
        });

        setTimeout(() => this.submit(), 100);
    });
</script>

Purchase Event

Track e-commerce purchases:

@using Vendr.Core.Models
@using Vendr.Core.Api
@inject IVendrApi VendrApi

@{
    var orderRef = Request.Query["order"];
    var order = VendrApi.GetOrder(Guid.Parse(orderRef));
}

@if (order != null)
{
    <script>
        // Track Purchase event
        fbq('track', 'Purchase', {
            value: @order.TotalPrice.Value.WithoutAdjustments.Value,
            currency: '@order.CurrencyCode',
            content_type: 'product',
            content_ids: [@string.Join(", ", order.OrderLines.Select(x => $"'{x.Sku}'"))],
            num_items: @order.OrderLines.Sum(x => x.Quantity)
        });
    </script>

    <h1>Order Confirmed!</h1>
    <p>Order Number: @order.OrderNumber</p>
}

Add to Cart Event

Track cart additions:

<button class="add-to-cart"
        data-product-id="@Model?.GetProperty("sku")?.Value<string>()"
        data-product-name="@Model?.Name"
        data-price="@Model?.GetProperty("price")?.Value<decimal>()">
    Add to Cart
</button>

<script>
    document.querySelector('.add-to-cart').addEventListener('click', function() {
        var productId = this.getAttribute('data-product-id');
        var productName = this.getAttribute('data-product-name');
        var price = parseFloat(this.getAttribute('data-price'));

        fbq('track', 'AddToCart', {
            content_ids: [productId],
            content_name: productName,
            content_type: 'product',
            value: price,
            currency: 'USD'
        });

        // Add to cart logic
        // ...
    });
</script>

Initiate Checkout Event

Track checkout start:

@using Vendr.Core.Api
@inject IVendrApi VendrApi

@{
    var order = VendrApi.GetCurrentOrder(Model.Value<Guid>("store"));
}

@if (order != null)
{
    <script>
        // Track InitiateCheckout event
        fbq('track', 'InitiateCheckout', {
            content_ids: [@string.Join(", ", order.OrderLines.Select(x => $"'{x.Sku}'"))],
            content_type: 'product',
            value: @order.TotalPrice.Value.WithoutAdjustments.Value,
            currency: '@order.CurrencyCode',
            num_items: @order.OrderLines.Sum(x => x.Quantity)
        });
    </script>
}

Search Event

Track site search:

@model YourProject.Models.SearchViewModel

@if (!string.IsNullOrEmpty(Model.Query))
{
    <script>
        fbq('track', 'Search', {
            search_string: '@Model.Query',
            content_category: 'site_search',
            num_results: @Model.Results.Count()
        });
    </script>
}

<form method="get" action="/search">
    <input type="text" name="query" value="@Model.Query" placeholder="Search..." />
    <button type="submit">Search</button>
</form>

View Content Event

Track content views:

@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage

<script>
    fbq('track', 'ViewContent', {
        content_name: '@Model?.Name',
        content_category: '@Model?.ContentType.Alias',
        content_ids: ['@Model?.Id'],
        content_type: '@(Model?.ContentType.Alias == "product" ? "product" : "article")'
    });
</script>

Server-Side Event Tracking (C#)

Create Meta Conversions API Service

IMetaConversionsService.cs:

using System.Threading.Tasks;
using System.Collections.Generic;

namespace YourProject.Services
{
    public interface IMetaConversionsService
    {
        Task TrackEvent(string eventName, Dictionary<string, object> eventData, string userEmail = null);
        Task TrackPurchase(string orderId, decimal value, string currency);
    }
}

MetaConversionsService.cs:

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;

namespace YourProject.Services
{
    public class MetaConversionsService : IMetaConversionsService
    {
        private readonly HttpClient _httpClient;
        private readonly string _pixelId;
        private readonly string _accessToken;

        public MetaConversionsService(HttpClient httpClient, IConfiguration configuration)
        {
            _httpClient = httpClient;
            _pixelId = configuration["Analytics:MetaPixel:PixelId"];
            _accessToken = configuration["Analytics:MetaPixel:AccessToken"];
        }

        public async Task TrackEvent(
            string eventName,
            Dictionary<string, object> eventData,
            string userEmail = null)
        {
            var payload = new
            {
                data = new[]
                {
                    new
                    {
                        event_name = eventName,
                        event_time = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
                        action_source = "website",
                        user_data = new
                        {
                            em = !string.IsNullOrEmpty(userEmail)
                                ? HashSHA256(userEmail.ToLowerInvariant())
                                : null
                        },
                        custom_data = eventData
                    }
                }
            };

            var json = JsonSerializer.Serialize(payload);
            var content = new StringContent(json, Encoding.UTF8, "application/json");

            var url = $"https://graph.facebook.com/v18.0/{_pixelId}/events?access_token={_accessToken}";

            var response = await _httpClient.PostAsync(url, content);
            response.EnsureSuccessStatusCode();
        }

        public async Task TrackPurchase(string orderId, decimal value, string currency)
        {
            await TrackEvent("Purchase", new Dictionary<string, object>
            {
                { "value", value },
                { "currency", currency },
                { "order_id", orderId }
            });
        }

        private string HashSHA256(string input)
        {
            using var sha256 = SHA256.Create();
            var bytes = Encoding.UTF8.GetBytes(input);
            var hash = sha256.ComputeHash(bytes);
            return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
        }
    }
}

Register Service in Startup

Program.cs:

using YourProject.Services;

var builder = WebApplication.CreateBuilder(args);

builder.CreateUmbracoBuilder()
    .AddBackOffice()
    .AddWebsite()
    .AddDeliveryApi()
    .AddComposers()
    .Build();

// Register Meta Conversions API Service
builder.Services.AddHttpClient<IMetaConversionsService, MetaConversionsService>();

var app = builder.Build();

// ... rest of configuration

Track Events from Controllers

ContactController.cs:

using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Web.Common.Controllers;
using YourProject.Services;

namespace YourProject.Controllers
{
    public class ContactController : SurfaceController
    {
        private readonly IMetaConversionsService _metaService;

        public ContactController(
            IMetaConversionsService metaService,
            /* other dependencies */)
        {
            _metaService = metaService;
        }

        [HttpPost]
        public async Task<IActionResult> SubmitForm(ContactFormModel model)
        {
            if (ModelState.IsValid)
            {
                // Process form
                // ...

                // Track server-side event
                await _metaService.TrackEvent("Lead", new Dictionary<string, object>
                {
                    { "content_name", "Contact Form" },
                    { "value", 10.00 },
                    { "currency", "USD" }
                }, model.Email);

                return RedirectToCurrentUmbracoPage();
            }

            return CurrentUmbracoPage();
        }
    }
}

Umbraco Forms Integration

Track Form Submissions

FormEventHandler.cs:

using Umbraco.Forms.Core.Services.Notifications;
using Microsoft.Extensions.Logging;
using YourProject.Services;

namespace YourProject.EventHandlers
{
    public class FormEventHandler : INotificationHandler<FormSubmittedNotification>
    {
        private readonly IMetaConversionsService _metaService;
        private readonly ILogger<FormEventHandler> _logger;

        public FormEventHandler(
            IMetaConversionsService metaService,
            ILogger<FormEventHandler> logger)
        {
            _metaService = metaService;
            _logger = logger;
        }

        public async Task Handle(
            FormSubmittedNotification notification,
            CancellationToken cancellationToken)
        {
            try
            {
                var form = notification.Form;
                var record = notification.Record;

                // Extract email from form fields
                var emailField = record.RecordFields
                    .FirstOrDefault(f => f.Alias.Contains("email", StringComparison.OrdinalIgnoreCase));

                var email = emailField?.ValuesAsString();

                // Determine event based on form name
                var eventName = form.Name.Contains("contact", StringComparison.OrdinalIgnoreCase)
                    ? "Lead"
                    : "CompleteRegistration";

                await _metaService.TrackEvent(eventName, new Dictionary<string, object>
                {
                    { "content_name", form.Name },
                    { "form_id", form.Id.ToString() }
                }, email);

                _logger.LogInformation($"Tracked Meta Pixel event: {eventName} for form: {form.Name}");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to track Meta Pixel event");
            }
        }
    }
}

Client-Side Form Tracking

@using Umbraco.Forms.Web
@model Umbraco.Forms.Web.Models.FormViewModel

<script>
    var formElement = document.querySelector('.umbraco-forms-form');

    // Track form start
    var formStarted = false;
    formElement.querySelectorAll('input, textarea, select').forEach(function(field) {
        field.addEventListener('focus', function() {
            if (!formStarted) {
                formStarted = true;

                fbq('trackCustom', 'FormStart', {
                    form_id: '@Model.FormId',
                    form_name: '@Model.FormName'
                });
            }
        });
    });

    // Track form submission
    formElement.addEventListener('submit', function() {
        fbq('track', 'Lead', {
            content_name: '@Model.FormName',
            content_category: 'umbraco_form'
        });
    });
</script>

Track Member Events

Member Registration

using Umbraco.Cms.Core.Security;
using YourProject.Services;

namespace YourProject.Controllers
{
    public class MemberController : SurfaceController
    {
        private readonly IMemberManager _memberManager;
        private readonly IMetaConversionsService _metaService;

        public MemberController(
            IMemberManager memberManager,
            IMetaConversionsService metaService,
            /* other dependencies */)
        {
            _memberManager = memberManager;
            _metaService = metaService;
        }

        [HttpPost]
        public async Task<IActionResult> Register(RegisterModel model)
        {
            if (ModelState.IsValid)
            {
                var identityUser = MemberIdentityUser.CreateNew(
                    model.Email,
                    model.Email,
                    "Member",
                    true,
                    model.Name
                );

                var result = await _memberManager.CreateAsync(identityUser, model.Password);

                if (result.Succeeded)
                {
                    // Track registration
                    await _metaService.TrackEvent("CompleteRegistration", new Dictionary<string, object>
                    {
                        { "content_name", "Member Registration" },
                        { "status", "completed" },
                        { "value", 25.00 },
                        { "currency", "USD" }
                    }, model.Email);

                    return RedirectToAction("Welcome");
                }
            }

            return CurrentUmbracoPage();
        }
    }
}

Custom Events

Track Video Engagement

<video id="product-video" controls>
    <source src="@Model?.Value("videoUrl")" type="video/mp4">
</video>

<script>
    var video = document.getElementById('product-video');
    var quartiles = [0.25, 0.5, 0.75, 1];
    var firedQuartiles = [];

    video.addEventListener('timeupdate', function() {
        var progress = video.currentTime / video.duration;

        quartiles.forEach(function(quartile) {
            if (progress >= quartile && !firedQuartiles.includes(quartile)) {
                firedQuartiles.push(quartile);

                fbq('trackCustom', 'VideoProgress', {
                    video_title: '@Model?.Value("videoTitle")',
                    progress_percent: quartile * 100,
                    content_id: '@Model?.Id'
                });
            }
        });
    });

    video.addEventListener('ended', function() {
        fbq('trackCustom', 'VideoComplete', {
            video_title: '@Model?.Value("videoTitle")',
            content_id: '@Model?.Id'
        });
    });
</script>

Track File Downloads

@{
    var pdfFile = Model?.Value<IPublishedContent>("downloadablePdf");
}

@if (pdfFile != null)
{
    <a href="@pdfFile.Url()"
       class="download-link"
       data-file-name="@pdfFile.Name"
       download>
        Download PDF
    </a>

    <script>
        document.querySelector('.download-link').addEventListener('click', function() {
            fbq('trackCustom', 'FileDownload', {
                file_name: this.getAttribute('data-file-name'),
                file_type: 'pdf',
                content_id: '@Model?.Id'
            });
        });
    </script>
}

Track Newsletter Signup

<form id="newsletter-form" method="post">
    <input type="email" name="email" placeholder="Email" required />
    <button type="submit">Subscribe</button>
</form>

<script>
    document.getElementById('newsletter-form').addEventListener('submit', function(e) {
        e.preventDefault();

        fbq('track', 'Lead', {
            content_name: 'Newsletter Signup',
            content_category: 'newsletter',
            value: 5.00,
            currency: 'USD'
        });

        setTimeout(() => this.submit(), 100);
    });
</script>

Advanced Event Tracking

Track Scroll Depth

<script>
    var scrollDepths = [25, 50, 75, 100];
    var triggeredDepths = [];

    window.addEventListener('scroll', function() {
        var scrollPercent = (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100;

        scrollDepths.forEach(function(depth) {
            if (scrollPercent >= depth && !triggeredDepths.includes(depth)) {
                triggeredDepths.push(depth);

                fbq('trackCustom', 'ScrollDepth', {
                    scroll_depth: depth,
                    content_name: '@Model?.Name',
                    content_type: '@Model?.ContentType.Alias'
                });
            }
        });
    });
</script>

Track Time on Page

<script>
    var startTime = new Date().getTime();

    window.addEventListener('beforeunload', function() {
        var endTime = new Date().getTime();
        var timeOnPage = Math.round((endTime - startTime) / 1000);

        if (timeOnPage > 30) { // Only track if > 30 seconds
            fbq('trackCustom', 'TimeOnPage', {
                time_seconds: timeOnPage,
                content_name: '@Model?.Name',
                content_id: '@Model?.Id'
            });
        }
    });
</script>

Event Deduplication

Prevent duplicate events when using both client-side and server-side tracking:

Client-Side:

<script>
    // Generate event ID
    var eventId = 'event_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);

    fbq('track', 'Purchase', {
        value: @order.Total,
        currency: '@order.Currency'
    }, {
        eventID: eventId
    });

    // Store event ID for server-side deduplication
    document.cookie = 'meta_event_id=' + eventId + '; path=/';
</script>

Server-Side:

public async Task TrackPurchase(Order order, string eventId = null)
{
    var payload = new
    {
        data = new[]
        {
            new
            {
                event_name = "Purchase",
                event_time = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
                event_id = eventId ?? Guid.NewGuid().ToString(), // Use same ID as client-side
                action_source = "website",
                user_data = new
                {
                    em = HashSHA256(order.CustomerEmail)
                },
                custom_data = new
                {
                    value = order.Total,
                    currency = order.Currency,
                    order_id = order.OrderNumber
                }
            }
        }
    };

    // Send to Conversions API
    // ...
}

Testing and Debugging

Enable Test Events

@inject Microsoft.Extensions.Hosting.IHostEnvironment HostEnvironment

<script>
    @if (HostEnvironment.IsDevelopment())
    {
        <text>
        // Log all events to console
        var originalFbq = window.fbq;
        window.fbq = function() {
            console.log('Meta Pixel Event:', arguments);
            originalFbq.apply(this, arguments);
        };
        </text>
    }
</script>

Use Meta Pixel Helper

  1. Install Meta Pixel Helper Chrome extension
  2. Navigate to your Umbraco site
  3. Perform actions that trigger events
  4. Click extension icon to see events fired

Test in Events Manager

  1. Navigate to Meta Events Manager
  2. Select your Pixel
  3. Click Test Events
  4. Enter test event name
  5. Verify events appear in real-time

Common Issues

Events Not Firing

Cause: JavaScript error or incorrect syntax Solution:

  • Check browser console for errors
  • Verify fbq function exists
  • Ensure Pixel initialized before tracking events

Duplicate Events

Cause: Multiple event calls or page refresh Solution:

  • Implement event deduplication with event IDs
  • Use session storage to track fired events

Server-Side Events Not Appearing

Cause: Missing Access Token or incorrect Pixel ID Solution:

  • Verify Access Token in appsettings.json
  • Check Pixel ID matches Events Manager
  • Review Conversions API response for errors

Next Steps