Nathaniel Nunes

Adding Custom Request Headers to Umbraco Forms Workflow

·Nathaniel Nunes

What's Happening

Happy New Year everyone, it's my first blog post of 2025 and, I am eager to share with you all what I have been tinkering with these past few days.

I have been working with Umbraco Forms and looking into how we can send the submitted form data to a third party REST API.

After reading the docs, I learnt that we can use the Send Form to URL Workflow Type to achieve this.

My REST API (which can be found here), uses an Authorization Filter which authenticates requests by means of an X-API-Key in the Request Header. However, none of the Umbraco Forms Workflow Types support adding in custom request headers.

This can be easily solved by creating our own Custom Workflow Type.

Project Setup

I needed a quick Umbraco Setup with a frontend, so I fired up the brilliant Package Script Writer tool written by my good friend and colleague, Paul Seal.

This tool gives us an easy script which allows us to customize our Umbraco setup and install it.

So, I selected Umbraco 13 LTS with the Clean Starter Kit and added Umbraco Forms. Click on this link, which will preload the commands which I have used.

Package Script Writer in action
Package Script Writer in action

Creating a custom Workflow Type

I referred to the Umbraco Docs to help me get started on creating a simple Workflow Type.

After understanding the basics, I decided to create a copy of the inbuilt Send Form to URL Workflow Type and write some custom code to allow the Content Editor to add in the Request Headers.

I opened up dotPeek, added the Umbraco.Forms.dll, Umbraco.Forms.Core.dll and Umbraco.Forms.Core.Providers.dll from the bin folder of my project and began to search for the Send Form to URL Workflow type.

Taking a peek using dotPeek
Taking a peek using dotPeek

The workflow type was present in the PostToUrl class under the Umbraco.Forms.Core.Providers.WorkflowTypes namespace.

Creating the Field

I created a class in my Umbraco Project named PostToUrlWithRequestHeaders.cs which was basically a replica of the PostToUrl class.

Do remember to change the Workflow type information such as Name, Alias, Id and Description in the PostToUrlWithRequestHeaders constructor

public PostToUrlWithRequestHeaders(
    ILogger<PostToUrlWithRequestHeaders> logger,
    IUmbracoHelperAccessor umbracoHelperAccessor,
    IPublishedUrlProvider publishedUrlProvider,
    IHttpClientFactory httpClientFactory)
{
    this._logger = logger;
    this._umbracoHelperAccessor = umbracoHelperAccessor;
    this._publishedUrlProvider = publishedUrlProvider;
    this.Id = new Guid("6BA32B89-13B8-4DC9-904F-4A459BA4BCBC");
    this.Name = "Send form to URL with Request Headers";
    this.Alias = "sendFormToUrlWithRequestHeaders";
    this.Description = "Sends the form to a URL, with custom Request Headers, either as a HTTP POST or GET";
    this.Icon = "icon-paper-plane";
    this.Group = "Legacy";
    this._httpClientFactory = httpClientFactory;
}

Let us create a field that stores our Request Headers. We will use the FieldMapper view which will give allow us to add the Header name and Header value.

[Setting("Request Header Fields", Description = "Add the Request Headers to be sent. Add the Header name to 'Alias' and Header value to 'Static Value' textboxes", View = "FieldMapper", SupportsPlaceholders = true, DisplayOrder = 60)]
public virtual string RequestHeaderFields { get; set; } = string.Empty;

Reading the Headers and Execution

The ExecuteAsync method of this workflow type is triggered when we submit our Form and reach this stage of the Workflow. Here let us write some code which extracts the data from the Request Header fields and store in a nice Dictionary.

This is done using the code below in ExecuteAsync

Dictionary<string, string> requestHeaders = new Dictionary<string, string>();

if (!string.IsNullOrWhiteSpace(this.RequestHeaderFields))
{
    IEnumerable<FieldMapping> source = JsonConvert.DeserializeObject<IEnumerable<FieldMapping>>(
        this.RequestHeaderFields,
        FormsJsonSerializerSettings.Default
    );

    if (source != null)
    {
        requestHeaders = AddRequestHeaderMappings(source);
    }
}

/// <summary>
/// Map the FieldMapping array to a Dictionary that holds the Request Headers
/// </summary>
/// <param name="mappings"></param>
/// <returns></returns>
private Dictionary<string, string> AddRequestHeaderMappings(IEnumerable<FieldMapping> mappings)
{
    Dictionary<string, string> requestHeaders = new Dictionary<string, string>();
    try
    {
        if (mappings != null && mappings.Any())
        {
            foreach (FieldMapping mapping in mappings)
            {
                string requestHeaderName = mapping.Alias;
                string requestHeaderValue = mapping.StaticValue;

                if (string.IsNullOrWhiteSpace(requestHeaderName))
                {
                    this._logger.LogWarning($"Workflow {this.Workflow?.Name}: The Request Header Name field is null");
                    continue;
                }

                if (string.IsNullOrWhiteSpace(requestHeaderValue))
                {
                    this._logger.LogWarning($"Workflow {this.Workflow?.Name}: The Request Header:{requestHeaderName} is not having a value");
                    continue;
                }

                requestHeaders.Add(requestHeaderName, requestHeaderValue);
            }
        }
    }
    catch (Exception ex)
    {
        this._logger.LogError(ex, $"Workflow {this.Workflow?.Name}: There was a problem parsing the Request Headers");
    }
    return requestHeaders;
}

Finally, let us go ahead and add these Request Headers to the HTTP Request using the code below:

foreach (var header in requestHeaders)
{
    request.Headers.Add(header.Key, header.Value);
}

Registering the Workflow Type

From the code perspective, all that's left now is to register our Workflow Type, so that we can see it in the Umbraco Forms backoffice section. We do this by registering the workflow type in the Umbraco DI container.

public class UmbracoFormsComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.WithCollectionBuilder<WorkflowCollectionBuilder>()
               .Add<PostToUrlWithRequestHeaders>();
    }
}

Configuring the Workflow in the Backoffice

  1. Build the project, run it, and go to the Forms section in the Umbraco backoffice.

  2. If you do not have a form, create it. Then scroll to the bottom of the page and click on configure workflow.

Configure the Workflow
Configure the Workflow
  1. A sidebar will open, in the On Submit Workflow, click on Add workflow
Add the Workflow type
Add the Workflow type
  1. You will now see our newly created Workflow Type, Send form to URL with Request Headers
Our newly created workflow type
Our newly created workflow type
  1. Give a name to the Workflow type.
  2. Activate it.
  3. Enable the Include Sensitive Data toggle.
  4. Paste in the URL of your API endpoint which will receive the submitted form data.
  5. Select the HTTP Method, in our case I will go with POST.
Step 5 to Step 9
Step 5 to Step 9
  1. Map the form fields to the payload, which will be received at the API endpoint.
Map the form fields
Map the form fields
  1. Scroll down to the **Request Header Fields* and click on the **Add mapping** button.
Adding our Request Header fields
Adding our Request Header fields
  1. We can now begin adding the request header fields. Add the Header name into the Alias column and the value of this Header into the Static value column.
Mapping of the Request Header fields
Mapping of the Request Header fields
  1. Finally, click on Submit and Save the Workflow.

Submitting the Form

In this example, I have added my Form to the Content Page document type. So, lets load up a page which has this form and submit it.

Submitting the Form
Submitting the Form

If I open up my API project window, we can see that it has triggered the endpoint and authorized successfully using the X-API-KEY header. We are able to retrieve the form data and read the header values as well.

API Endpoint has been triggered
API Endpoint has been triggered

Finito

This should work with all version of Umbraco, I hope you find this useful, and it helps you in your projects.

All the Workflow type code is available for reference in a GitHub repository.

The .NET API code is available as well over here

If you face any issues, please reach out to me or log an issue/feature request.

References

I have referred to some nice articles and documentation which has helped me come up with this solution. Please find them below: