On this page
.NET / C#
Last updated
Send vTilt events from .NET code (ASP.NET Core, console apps, Azure Functions) by POSTing to the ingest API.
There's no native .NET SDK yet, but vTilt's wire format is a plain JSON POST so any C# code can emit events. The pattern below uses HttpClient + System.Text.Json; the same shape works inside ASP.NET Core, ASP.NET MVC, Blazor Server, Azure Functions, console apps, and worker services.
#1. Configure
# .env / appsettings.json
VTILT_TOKEN=YOUR_PROJECT_TOKEN
VTILT_HOST=https://www.vtilt.comtext
#2. Capture client
A typed HttpClient exposed through IHttpClientFactory. Captures are awaited inside Task.Run so handlers don't block on network IO.
// Vtilt/VtiltClient.cs
using System.Net.Http.Json;
public sealed class VtiltClient
{
private readonly HttpClient _http;
private readonly string _token;
private readonly ILogger<VtiltClient> _log;
public VtiltClient(HttpClient http, IConfiguration config, ILogger<VtiltClient> log)
{
_http = http;
_http.BaseAddress = new Uri(config["VTILT_HOST"] ?? "https://www.vtilt.com");
_http.Timeout = TimeSpan.FromSeconds(2);
_token = config["VTILT_TOKEN"] ?? throw new InvalidOperationException("VTILT_TOKEN missing");
_log = log;
}
public void Capture(string distinctId, string @event, IDictionary<string, object>? properties = null)
{
var payload = new
{
api_key = _token,
@event = @event,
distinct_id = distinctId,
properties = properties ?? new Dictionary<string, object>(),
};
_ = Task.Run(async () =>
{
try
{
await _http.PostAsJsonAsync("/api/e", payload);
}
catch (Exception ex)
{
_log.LogWarning(ex, "vtilt capture failed");
}
});
}
public void Identify(string distinctId, IDictionary<string, object> properties)
{
Capture(distinctId, "$identify", new Dictionary<string, object> { ["$set"] = properties });
}
}csharp
// Program.cs
builder.Services.AddHttpClient<VtiltClient>();csharp
#3. Capture events
public class CheckoutController : ControllerBase
{
private readonly VtiltClient _vtilt;
public CheckoutController(VtiltClient vtilt) => _vtilt = vtilt;
[HttpPost("/checkout")]
public IActionResult Complete([FromBody] CheckoutRequest req)
{
_vtilt.Identify(req.UserId, new Dictionary<string, object> { ["plan"] = "pro" });
_vtilt.Capture(req.UserId, "purchase_completed", new Dictionary<string, object>
{
["amount"] = req.Amount,
["currency"] = "USD",
});
return Ok(new { ok = true });
}
}csharp
#4. Azure Functions
[Function("Checkout")]
public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req)
{
var data = await req.ReadFromJsonAsync<CheckoutRequest>();
_vtilt.Capture(data!.UserId, "purchase_completed", new Dictionary<string, object> { ["amount"] = data.Amount });
var res = req.CreateResponse(HttpStatusCode.OK);
await res.WriteAsJsonAsync(new { ok = true });
return res;
}csharp
#Next steps
- Identify & alias — full identification model.
- Event forwarding — fan events out to GA4, Meta CAPI, PostHog.
- Reverse proxy — block-resistant ingestion.
- Common mistakes — integration pitfalls to avoid before you ship.