Skip to content

C# (HttpClient) examples — Ludex ingestion

Bare .NET HttpClient — no Ludex SDK. Targets POST /v1/ingest/event and POST /v1/ingest/batch with JSON and 202 success responses.

Add System.Text.Json (included in modern .NET) for serialization.


Configuration

using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

const string BaseUrl = "https://ingest.ludexstudio.com"; // or the URL Ludex provides
const string ApiKey = Environment.GetEnvironmentVariable("LUDEX_API_KEY")!;
const string ProjectId = Environment.GetEnvironmentVariable("LUDEX_PROJECT_ID")!;
const string EnvironmentName = Environment.GetEnvironmentVariable("LUDEX_ENVIRONMENT")!;

EnvironmentName must match the credential’s stored environment id (same as the environment field in JSON).


Build a shared HttpClient

Reuse one client per process (socket pooling). Set the API key on default headers or per request.

using var http = new HttpClient { BaseAddress = new Uri(BaseUrl.TrimEnd('/') + "/") };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ApiKey);

Single event (scope: ingest:events)

Use explicit dictionary keys so the JSON matches the API (event, snake_case fields) without relying on naming policies:

var payload = new Dictionary<string, object?>
{
    ["project_id"] = ProjectId,
    ["environment"] = EnvironmentName,
    ["event"] = new Dictionary<string, object?>
    {
        ["event_name"] = "level_complete",
        ["timestamp"] = DateTime.UtcNow.ToString("o"),
        ["player_id"] = "player-uuid-123",
        ["session_id"] = Guid.NewGuid().ToString(),
        ["platform"] = "Windows",
        ["properties"] = new Dictionary<string, object?> { ["level"] = 3, ["score"] = 1200 },
        ["sdk"] = new Dictionary<string, string>
        {
            ["name"] = "csharp-httpclient-example",
            ["version"] = "1.0.0",
        },
        ["event_id"] = Guid.NewGuid().ToString(),
    },
};

var json = JsonSerializer.Serialize(payload);

using var req = new HttpRequestMessage(HttpMethod.Post, "v1/ingest/event")
{
    Content = new StringContent(json, Encoding.UTF8, "application/json"),
};
req.Headers.TryAddWithoutValidation("Idempotency-Key", $"idem-{Guid.NewGuid():N}");
req.Headers.TryAddWithoutValidation("X-Correlation-Id", "corr-demo-001");

using var resp = await http.SendAsync(req);
var body = await resp.Content.ReadAsStringAsync();
resp.EnsureSuccessStatusCode(); // throws if not 2xx — ingestion uses 202
Console.WriteLine((int)resp.StatusCode);
Console.WriteLine(body);

Batch (scope: ingest:batch)

var batch = new Dictionary<string, object?>
{
    ["project_id"] = ProjectId,
    ["environment"] = EnvironmentName,
    ["events"] = new object[]
    {
        new Dictionary<string, object?>
        {
            ["event_name"] = "match_started",
            ["timestamp"] = DateTime.UtcNow.ToString("o"),
            ["session_id"] = "sess-1",
            ["properties"] = new Dictionary<string, object?> { ["mode"] = "ranked" },
        },
        new Dictionary<string, object?>
        {
            ["event_name"] = "match_ended",
            ["timestamp"] = DateTime.UtcNow.ToString("o"),
            ["session_id"] = "sess-1",
            ["properties"] = new Dictionary<string, object?> { ["duration_sec"] = 900 },
        },
    },
};

var batchJson = JsonSerializer.Serialize(batch);

using var batchReq = new HttpRequestMessage(HttpMethod.Post, "v1/ingest/batch")
{
    Content = new StringContent(batchJson, Encoding.UTF8, "application/json"),
};

using var batchResp = await http.SendAsync(batchReq);
var batchBody = await batchResp.Content.ReadAsStringAsync();
batchResp.EnsureSuccessStatusCode();
Console.WriteLine(batchBody);

Notes

  • 202 Accepted is success; do not treat non-200 as failure if you only check StatusCode == HttpStatusCode.OK.
  • On 429 / 5xx, apply backoff and retry; on 401 / 403 / 422, fix credentials or payload.
  • Keep event_id stable for the same logical event when retrying after timeouts.