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
- Install Meta Pixel Helper Chrome extension
- Navigate to your Umbraco site
- Perform actions that trigger events
- Click extension icon to see events fired
Test in Events Manager
- Navigate to Meta Events Manager
- Select your Pixel
- Click Test Events
- Enter test event name
- Verify events appear in real-time
Common Issues
Events Not Firing
Cause: JavaScript error or incorrect syntax Solution:
- Check browser console for errors
- Verify
fbqfunction 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
- Meta Pixel Setup - Configure Meta Pixel
- Troubleshooting Tracking Issues - Debug tracking problems
- GTM Setup - Implement Google Tag Manager