- Published on
Implementing a Webhook for Sitecore Cloud Forms with ASP.NET Core
- Authors
- Name
- Jorge Lusar
Sitecore Cloud Forms allows you to easily capture form submissions from your websites. But often you’ll want to send those submissions somewhere else — for example, into your own backend system for processing, persistence, or analytics.
This is where webhooks come in. A webhook is simply an HTTP endpoint that Sitecore calls when a form is submitted. Let’s walk through how I implemented a webhook in ASP.NET Core to handle form submissions.
The Payload from Sitecore Cloud Forms
When Sitecore Cloud Forms submits data to your webhook, it posts a JSON payload. For example, a contact form submission might look like this:
{
"name": "Jorge",
"lastName": "Lusar",
"email": "jorge@test.com",
"comment": "My comment",
"domain": "forms.sitecorecloud.io"
}
Alongside the payload, Sitecore sends a set of useful request headers:
{
"Date": "<timestamp when the data was submitted>",
"X-Forwarded-For": "<the address of the end user, if available>",
"User-Agent": "<browser where the data was submitted from, if available>",
"Referer": "<request referer, if available>",
"X-FormId": "someguid-region",
"X-FormName": "Contact Form"
}
These headers help you track submissions (e.g., form ID, client IP, referer).
Defining Models for the Payload and Headers
First, I created simple C# models to represent the incoming form data and headers.
Form request headers:
public class FormRequestHeaders
{
public string Date { get; set; }
public string XForwardedFor { get; set; }
public string UserAgent { get; set; }
public string Referer { get; set; }
public string FormId { get; set; }
public string FormName { get; set; }
}
Contact form payload:
[DataContract]
public class ContactForm
{
[DataMember(Name = "name")]
[Required, MaxLength(50)]
public required string Name { get; set; }
[DataMember(Name = "email")]
[Required, EmailAddress, DataType(DataType.EmailAddress)]
[MaxLength(50)]
[RegularExpression(RegularExpressions.EmailCharacters)]
public required string Email { get; set; }
[DataMember(Name = "comment")]
[MaxLength(200)]
public string? Comment { get; set; }
[DataMember(Name = "domain")]
[Required, MaxLength(50)]
public required string Domain { get; set; }
}
Here I used data annotations to validate the payload automatically when ASP.NET Core binds it.
Implementing the Webhook Endpoint
Now let’s create an API controller to receive the form submissions.
[ApiController]
[Route("api/form")]
public class FormController(ILogger<FormController> logger, IFormService formService) : ControllerBase
{
private readonly ILogger<FormController> logger = logger;
private readonly IFormService formService = formService;
private static readonly string[] RequiredHeaders =
[
"Date",
"X-Forwarded-For",
"User-Agent",
"Referer",
"X-FormId",
"X-FormName"
];
[HttpPost]
[Route("contactform")]
public async Task<IActionResult> Contact([FromBody] ContactForm payload)
{
var formRequestHeaders = GetFormRequestHeaders(HttpContext);
if (formRequestHeaders == null)
{
return BadRequest(ModelState);
}
if (!ModelState.IsValid)
{
logger.LogError("Invalid model state for Generic form submission.");
return BadRequest(ModelState);
}
await formService.SaveFormAsync(formRequestHeaders, payload);
logger.LogInformation("Generic form: Name {Name} in Email {Email}", payload.Name, payload.Email);
return Ok("Form received successfully.");
}
private FormRequestHeaders? GetFormRequestHeaders(HttpContext context)
{
var headers = context.Request.Headers;
var missing = new List<string>();
var formHeaders = new FormRequestHeaders();
foreach (var h in RequiredHeaders)
{
string value = headers.TryGetValue(h, out var v) && !string.IsNullOrWhiteSpace(v)
? v.ToString()
: string.Empty;
switch (h)
{
case "Date": formHeaders.Date = value; break;
case "X-Forwarded-For": formHeaders.XForwardedFor = value; break;
case "User-Agent": formHeaders.UserAgent = value; break;
case "Referer": formHeaders.Referer = value; break;
case "X-FormId": formHeaders.FormId = value; break;
case "X-FormName": formHeaders.FormName = value; break;
}
if (string.IsNullOrWhiteSpace(value))
missing.Add(h);
}
if (missing.Count > 0)
{
logger.LogWarning("Missing required headers: {Headers}", string.Join(", ", missing));
return null;
}
return formHeaders;
}
}
A few important details:
- The
[ApiController]
attribute automatically validates the model based on data annotations. - I check headers explicitly, since they aren’t model-bound.
IFormService
is just an abstraction to save or process the form data — you could persist to a database, forward to another API, or log it.
Testing the Webhook
To test, you can use a tool like Postman or cURL to simulate the request.
Example cURL command:
curl -X POST https://localhost:5001/api/form/contactform
-H "Content-Type: application/json"
-H "Date: Thu, 21 Aug 2025 12:34:56 GMT"
-H "X-Forwarded-For: 192.168.0.1"
-H "User-Agent: Mozilla/5.0"
-H "Referer: https://localhost"
-H "X-FormId: someguid-region"
-H "X-FormName: Contact Form"
-d '{"name":"Jorge","email":"jorge@test.com","comment":"Hello!","domain":"forms.sitecorecloud.io"}'
If everything works, you should see:
Form received successfully.
Wrapping Up
With this setup, your ASP.NET Core application can securely receive and process Sitecore Cloud Form submissions.
You can extend this further by:
- Adding authentication (e.g., API key validation).
- Logging submissions to a database.
- Forwarding data to a CRM or marketing automation tool.
- Building analytics dashboards for form usage.