Real-Time FAQs: Updating Umbraco with the Management API

In a multi-brand setup, you might host one site in Umbraco and another site in a different CMS. You want consistent, up-to-date FAQ items across all brands. Whenever an FAQ is updated in the other system, we automatically push that change into Umbraco so both sites stay in sync without manual work.

How this works with the Umbraco Management API

When the other CMS, such as a site running on a different platform like Sitecore, saves or modifies a frequently asked question, it triggers a webhook that sends the updated data in JSON format. Our .NET application processes this payload and uses the Umbraco Management API to update the corresponding FAQ content node in Umbraco.

The Management API provides a secure way for external systems to interact with Umbraco through HTTP requests, authenticated using a Bearer token. This approach eliminates the need for special Umbraco NuGet packages or additional dependencies, allowing for a straightforward integration.

For more information on how to configure and work with the Management API, visit the official Umbraco Management API documentation.

Code example

Below is the core snippet from a .NET 9 minimal API (though you could use any language or version of .NET):

// -------------------------------------------------------------------------
// Configuration - Umbraco
// -------------------------------------------------------------------------
string umbracoHost = "https://localhost:44392";
string umbracoClientId = "umbraco-back-office-my-client";
string umbracoClientSecret = "umbraco-client-secret";
string umbracoFaqDocId = "e75d2a2b-295c-48e6-bea0-f83c5100682b";
string umbracoTitleAlias = "title";
string umbracoTextAlias = "text";

// -------------------------------------------------------------------------
// Endpoint: Receive Sitecore Webhook -> Update Umbraco
// -------------------------------------------------------------------------
app.MapPost("/api/webhooks/sitecore", async (HttpContext context, ILogger<Program> logger) =>
{
    // 1) Read the raw JSON body
    var doc = await context.Request.ReadFromJsonAsync<JsonDocument>();

    // 2) Parse JSON for Title/Text. You can get it from:
    //    - "Item.VersionedFields"
    string? titleFromSitecore = null;
    string? textFromSitecore = null;

    // Look in "VersionedFields" array
    if (doc.RootElement.TryGetProperty("Item", out var itemElem) &&
        itemElem.TryGetProperty("VersionedFields", out var versionedFieldsElem) &&
        versionedFieldsElem.ValueKind == JsonValueKind.Array)
    {
        foreach (var fieldElem in versionedFieldsElem.EnumerateArray())
        {
            if (!fieldElem.TryGetProperty("Id", out var fieldIdElem)) continue;
            var fieldId = fieldIdElem.GetString() ?? string.Empty;

            // Title field
            if (fieldId.Equals("75577384-3c97-45da-a847-81b00500e250", StringComparison.OrdinalIgnoreCase))
            {
                if (fieldElem.TryGetProperty("Value", out var valElem))
                {
                    titleFromSitecore = valElem.GetString();
                }
            }
            // Text field
            else if (fieldId.Equals("a60acd61-a6db-4182-8329-c957982cec74", StringComparison.OrdinalIgnoreCase))
            {
                if (fieldElem.TryGetProperty("Value", out var valElem))
                {
                    textFromSitecore = valElem.GetString();
                }
            }
        }
    }

    // 3) Update Umbraco using the parsed fields
    var httpFactory = context.RequestServices.GetRequiredService<IHttpClientFactory>();

    // (a) Acquire an Umbraco token (Client Credentials)
    var umbracoToken = await GetUmbracoTokenAsync(httpFactory, umbracoHost, umbracoClientId, umbracoClientSecret);

    // (b) Build the JSON for the Umbraco PUT
    //     We'll update "title" and "text".
    var valuesList = new List<object>();

    if (!string.IsNullOrEmpty(titleFromSitecore))
    {
        valuesList.Add(new
        {
            alias = umbracoTitleAlias, 
            value = titleFromSitecore
        });
    }
    if (!string.IsNullOrEmpty(textFromSitecore))
    {
        valuesList.Add(new
        {
            alias = umbracoTextAlias,
            value = textFromSitecore
        });
    }

    var bodyObject = new
    {
        values = valuesList.ToArray(),
        variants = new[]
        {
            new
            {
                name = "FAQ item"
            }
        }
    };

    var updateJson = JsonSerializer.Serialize(bodyObject, new JsonSerializerOptions
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        WriteIndented = true
    });

    // (c) Send the PUT to Umbraco
    var client = httpFactory.CreateClient();
    client.BaseAddress = new Uri(umbracoHost);
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", umbracoToken);

    var content = new StringContent(updateJson, Encoding.UTF8, "application/json");
    var response = await client.PutAsync($"/umbraco/management/api/v1/document/{umbracoFaqDocId}", content);
    var responseBody = await response.Content.ReadAsStringAsync();

    logger.LogInformation("Umbraco FAQ updated successfully");
    return Results.Ok("Sitecore data used to update Umbraco successfully!");
});

// -----------------------------------------------------------------------
// Helper: Acquire a token from Umbraco Management API (Client Credentials)
// -----------------------------------------------------------------------
static async Task<string?> GetUmbracoTokenAsync(
    IHttpClientFactory httpFactory,
    string umbracoHost,
    string clientId,
    string clientSecret)
{
    using var tokenClient = httpFactory.CreateClient();
    tokenClient.BaseAddress = new Uri(umbracoHost);

    var req = new ClientCredentialsTokenRequest
    {
        Address = "/umbraco/management/api/v1/security/back-office/token",
        ClientId = clientId,
        ClientSecret = clientSecret
    };

    var resp = await tokenClient.RequestClientCredentialsTokenAsync(req);
    return resp.AccessToken;
}

You can view all the code in this Gist.

Key Steps:

  1. Read JSON from the Sitecore webhook: Use ReadFromJsonAsync<JsonDocument>() to parse the incoming payload.
  2. Extract fields: Typically something like properties.title and properties.text.markup.
  3. Obtain Token: Use a simple client-credentials request to POST /umbraco/management/api/v1/security/back-office/token.
  4. PUT to Umbraco: Call PUT /umbraco/management/api/v1/document/{guid} with a Bearer <token> header.

When the request succeeds, Umbraco’s FAQ content node, for example "How do I reset my password?", is updated with the new text from the external system.

Short demo video

In the demo, you’ll see how content synchronization works between Sitecore and Umbraco using the Sitecore Authoring API and the Umbraco Management API: https://youtu.be/QHxpJ2EozBU

Conclusion

We have synchronized an FAQ into Umbraco without relying on any official Umbraco NuGet references. This can be done in .NET 9, Node.js, or any environment that can read JSON and perform HTTP requests. It is a real-time approach that keeps FAQs in sync for multi-brand sites with minimal overhead.

For the other direction, see how content edits in Umbraco can be synced back to Sitecore using the Sitecore Authoring API:
Seamless Sync: Updating Sitecore Using the Authoring API.

Edit in Sitecore and update in Umbraco
Edit in Sitecore and update in Umbraco

Sitecore webhook
Sitecore webhook