AX Integration: Royal Mail Shipping API Part 5 – Security

RoyalMail               Dyn-AX12_v_rgb

We are building an integration between the Royal Mail SOAP Shipping API and Microsoft Dynamics AX 2012 R2. In parts 1-3 we have built a C# component to add some helper methods for interacting with the Royal Mail client proxy. In part 4 we called this client proxy in X++ and used our helper methods to extend the headers we send with the request message.

Now that we have the service client, we need to add WS Security headers to the request message to pass the Royal Mail authentication.

CLR Error Messages

When use CLR objects in X++, error checking can be an issue. However there is a handy static method available AifUtil::getClrErrorMessage().

//Something with .Net objects.
errorMessage = AifUtil::getClrErrorMessage();
throw error (errorMessage);

WS Security

Web Service Security is an extension of the SOAP specification, the standard is published by the OASIS commitee that oversees various web standards.

The specification that Royal Mail uses is the Web Services Security
Username Token Profile version 1.0 a . This mandates a SOAP security header (added to the header of the message, rather than the headers of http headers of the request), including a username, a password digest made up of a SHA-1 hash of the password, created datetime and the nonce; a nonce and the created datetime.

In my opinion for the type of transaction that this security is being used for, it is a bit overkill. Other carrier APIs (Parcelforce, DHL, Yodel, Asendia) just use a username/password combination. After all this is not a financial transaction, and the most vulnerable data – the customers name and address is being sent unencrypted in plain text.

SHA-1 Hash

A SHA-1 Hash is a type of cryptographic message digest that is designed to require several pieces of information to be able to generate the message. It was designed by the NSA for various security protocols and was originally used for the protection of unclassified sensitive data. It has now been superseded by SHA-2, especially after SHA-1 was shown to be vulnerable to  collision attacks in 2015.

We can construct a simple function to have a password as follows:

private System.Byte[] getSHA1(Str _input)
System.Security.Cryptography.SHA1Managed sha1Managed;
System.Text.Encoding encoding;
System.Byte[] bytes;
str errorMessage;
sha1Managed = new System.Security.Cryptography.SHA1Managed();
encoding = System.Text.Encoding::get_Default();
bytes = sha1Managed.ComputeHash(encoding.GetBytes(_input));
errorMessage = AifUtil::getClrErrorMessage();
throw error (errorMessage);
return bytes;

view raw


hosted with ❤ by GitHub

Here we get the default encoding of the operating system, then encode the string in to a sequence of bytes. We pass this to the SHA1 hashing method available in the System.Security.Cryptography namespace, to get our message digest.


A nonce is a randomly generated number, that can only be used once, it is designed to protect against replay attacks using old communication data.

I had a little trouble with using the random function to produce a unique number, as I kept getting a similar number as had been produced before. So I decided to use a unique number that is available in AX tables the RecId. A RecId is unique for each row in a table.

I created an AX table that would hold details of each request, useful information like the SalesId, the request log file path, datetime created and whether the request received a response or not. Each time a message request is going to be sent I add a row to the table and retrieve the RecId, then use this to populate the nonce. I use the random function as a backup in case for some reason this table logging has failed and there is no RecId returned.

Then I update the table to add the nonce that was used for this request, which is useful to check what nonce is being used.

private str getNonce(RecId _recId)
str nonce;
System.Random random = new System.Random();
System.Int32 randomNumber = random.Next();
if(_recId > 0)
nonce = int642str(_recId);
nonce = randomNumber.ToString();
nonce = subStr(nonce, 1, 16);
return nonce;

view raw


hosted with ❤ by GitHub

Password Digest

The password digest is a SHA1 hash of the nonce + created datetime + hashed password. This is then converted to Base 64 for transport.

Security Header XML

Then we need to create the XML that we will add to the request message with the authentication details in.

private System.Xml.XmlDocument getSecurityHeaderXML(str _username, str _passwordDigest,
str _encodedNonce, str _creationDate)
System.Xml.XmlDocument xDoc;
System.Xml.XmlNode xmlRoot;
System.Xml.XmlElement securityElement;
System.Xml.XmlElement usernameTokenElement;
System.Xml.XmlElement usernameElement;
System.Xml.XmlElement passwordElement;
System.Xml.XmlElement nonceElement;
System.Xml.XmlElement createdElement;
str username = _username;
str passwordDigest = _passwordDigest;
str encodedNonce = _encodedNonce;
str creationDate = _creationDate;
xDoc = new System.Xml.XmlDocument();
xmlRoot = xDoc.CreateElement("root");
usernameTokenElement = xDoc.CreateElement("wsse", "UsernameToken",
usernameElement = xDoc.CreateElement("wsse", "Username",
passwordElement = xDoc.CreateElement("wsse", "Password",
nonceElement = xDoc.CreateElement("wsse", "Nonce",
createdElement = xDoc.CreateElement("wsu", "Created",
return xDoc;

Main Function

Putting everything together, we create the password digest, create the WSSE Security headers that are placed in the SOAP Envelope header, then store this in the client proxy’s SecurityHeaderType object.

private RoyalMail.SecurityHeaderType getSecurityHeaderType(RecId _recId = 0)
RoyalMail.SecurityHeaderType securityHeaderType = new RoyalMail.SecurityHeaderType();
str passwordDigest;
str userName;
System.Byte[] hashedPassword;
System.Byte[] digest;
str concatednatedDigestInput;
str encodedNonce;
int i;
Counter counter = 0;
System.Xml.XmlElement xDocElement;
System.Xml.XmlNodeList xChildNodes;
System.Xml.XmlElement[] xHeaders;
System.Xml.XmlDocument xDoc;
int childNodeCount;
System.Exception ex;
System.Collections.IEnumerator enumerator;
str creationDate = this.getSecurityCreatedDate();
str nonce = this.getNonce(_recId);
//Make a log of the Nonce used
this.updateRequestLogNonce(_recId, nonce);
userName = this.parmRMUserName();
hashedPassword = this.getSHA1(this.parmRMPassword());
concatednatedDigestInput = this.concatDigest(nonce, creationDate, hashedPassword);
digest = this.getSHA1(concatednatedDigestInput);
passwordDigest = System.Convert::ToBase64String(digest);
encodedNonce = System.Convert::ToBase64String(this.encodeToBytes(nonce));
xDoc = this.getSecurityHeaderXML(userName, passwordDigest, encodedNonce, creationDate);
xDocElement = xDoc.get_DocumentElement();
//Get the XMLNodelist of the document
xChildNodes = xDocElement.get_ChildNodes();
childNodeCount = xChildNodes.get_Count();
xHeaders = new System.Xml.XmlElement[childNodeCount]();
enumerator = xChildNodes.GetEnumerator();
//Convert XMLNodelist to an array of XmlElements
xHeaders.SetValue(enumerator.get_Current(), counter);
counter ++;
return securityHeaderType;


We have created a password digest and added the WS security headers to our request message.

Everything else that you need to do to send a request message (like add shipment data, receive the response and handle error messages) is relatively straight forward once you have this set up.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.