Add Basic Akismet Support to Your Umbraco Project

Unless you have been on a lengthy tech sabbatical, you're well aware of the impact spam has had on the net. In 2013, Kaspersky Labs reported that 75% of all email traffic in the world was spam. Another report, by web-security firm Incapsula, stated that 61.5% of all web traffic is actually generated by bots. As a human, looking for genuine information in a world of dirty data is becoming quite the challenge. And, quite honestly, I just don't think CAPTCHA solutions are as effective as they once were (not to mention they are typically not ADA friendly).

Enter Akismet, the spam-fighting juggernaut established in 2005 as a plugin for WordPress. This product has become the standard in spam prevention for bloggers around the world and has surpassed the 100 trillion mark with regard to blocked spam. That is definitely a serious accomplishment. To make it even better, Akismet provides an API that allows developers to take advantage of their special capabilities, regardless of their chosen platform (Akismet is based on PHP).

As an Umbraco developer, I obviously have a need for tools built on the .NET framework. There are actually a couple of open source libraries available that have implemented the Akismet API. However, I feel that there are a couple of reasons why I would rather "roll my own", so to speak. The Akismet API is simple. Ridiculously simple. By building your own implementation you have the ability to control how and where it operates. In addition, there are no concerns about a loss in support or delays in updates after the API has changed. As the developer, you have that control. Now, that doesn't mean I'm in favor of reinventing the wheel every time I need something. In fact, I'm very much in favor of using 3rd party solutions in many cases. I would certainly never try to write my own versions of jQuery or Umbraco. But, as I mentioned, the Akismet API is just so simple, I feel the benefits outweigh the risks.

There are only a few things you'll need to get this project started:

  • An Akismet API Key (easily created with a simple registration via the Akismet site)
  • A working knowledge of Umbraco surface controllers
  • A working knowledge of basic .NET web requests
  • A working knowledge of jQuery AJAX calls

To start, I created a few entries in the web.config appSettings to manage the key and path requirements for the current version of the API.

key="AkismetApiRoot" value=""
key="AkismetVerifyKeyPath" value="/1.1/verify-key"
key="AkismetCommentCheckPath" value="/1.1/comment-check"
key="AkismetApiKey" value="1a2b3c4d5e6"

Next, I created a few models to help manage my data. In typical MVC fashion, these are placed in a Models directory in the main project.

public class AkismetComment
	public string CommentType { get; set; }
	public string CommentAuthor { get; set; }
	public string CommentAuthorEmail { get; set; }
	public string CommentAuthorUrl { get; set; }
	public string CommentContent { get; set; }

public class AkismetResponse
	public string Value { get; set; }
	public string Message { get; set; }
	public bool Discard { get; set; }

Now, for the primary logic. The surface controller houses the main entry-point for the application, as well as the logic surrounding the web requests to the Akismet API. Since the workflow consists of only two required calls, only one public method is needed.

public class AkismetController : SurfaceController
	private static readonly string _apiRoot = ConfigurationManager.AppSettings["AkismetApiRoot"];
	private static readonly string _apiKey = ConfigurationManager.AppSettings["AkismetApiKey"];

	public JsonResult SubmitComment(AkismetComment comment)
		var status = "ok";
		var message = "";

		if (VerifyApiKey())
			var response = CheckComment(comment);
			if (response.Value == "true" && response.Discard == true)
				status = "discard";
				message = "This message has been confirmed as spam.";
			else if (response.Value == "true" && response.Discard == false)
				status = "review";
				message = "This message may be spam and should be reviewed.";
			else if (response.Value == "invalid")
				status = "error";
				message = response.Message;
			status = "invalid";
			message = "Your Akismet API Key is invalid.";

		return Json(new { Status = status, Message = message });

	private bool VerifyApiKey()
		var protocol = Request.Url.GetLeftPart(UriPartial.Scheme);
		var url = string.Format("{0}{1}{2}", protocol, _apiRoot, ConfigurationManager.AppSettings["AkismetVerifyKeyPath"]);
		var request = string.Format("key={0}&blog={1}", _apiKey, Server.UrlEncode(string.Format("{0}{1}", protocol, Request.Url.Host)));

		var response = PostRequest(url, request);

		return response.Value == "valid";

	private AkismetResponse CheckComment(AkismetComment comment)
		var protocol = Request.Url.GetLeftPart(UriPartial.Scheme);
		var url = string.Format("{0}{1}.{2}{3}", protocol, _apiKey, _apiRoot, ConfigurationManager.AppSettings["AkismetCommentCheckPath"]);
		var request = string.Format("blog={0}&user_ip={1}&user_agent={2}&referrer={3}&permalink={4}&comment_type={5}&comment_author={6}&comment_author_email={7}&comment_author_url={8}&comment_content={9}",
			Server.UrlEncode(string.Format("{0}{1}", protocol, Request.Url.Host)),

		return PostRequest(url, request);

	private AkismetResponse PostRequest(string url, string request)
		var akismetResponse = new AkismetResponse();

		var byteData = Encoding.UTF8.GetBytes(request);

		var req = (HttpWebRequest)WebRequest.Create(url);
		req.Method = "POST";
		req.UserAgent = "Umbraco/7.1 | Akismet/2.5.9";
		req.ContentType = "application/x-www-form-urlencoded";
		req.ContentLength = byteData.Length;

		using (var dataStream = req.GetRequestStream())
			dataStream.Write(byteData, 0, byteData.Length);

			using (var resp = (HttpWebResponse)req.GetResponse())
			using (var rs = new StreamReader(resp.GetResponseStream()))
				akismetResponse.Value = rs.ReadToEnd();
				akismetResponse.Discard = resp.Headers["X-akismet-pro-tip"] == "discard";
				akismetResponse.Message = resp.Headers["X-akismet-debug-help"];


		return akismetResponse;

Finally, the client-side logic. This example is based on a simple Contact Us form. It collects a Name, Email, and Message, which would then be delivered to the site owner via email. The implementation assumes that the information collected is independent of the Akismet system, so the form data is populated into a JSON object matching one of the Akismet models defined above.

var $form = $("#contactForm");
if ($form.valid()) {
	var data = {
		CommentType: "contact",
		CommentAuthor: $form.find("input#contactName").val(),
		CommentAuthorEmail: $form.find("input#contactEmail").val(),
		CommentContent: $form.find("textarea#contactMessage").val()

	$.post("/umbraco/surface/akismet/submitcomment", data, function (response) {
		if (response.Status == "ok" || response.Status == "review") {
			$.post("/umbraco/surface/contact/submitcontact", $form.serialize());

That's it. You now have a solid anti-spam solution for your project. It's basic, but it's simple. There are certainly several ways to improve on the implementation, with respect to Akismet's spam versus ham features, providing an interface for users to review before publishing, etc. However, this gets you up and running with a solid solution that's both effective and flexible.