ConfuedProblemSolver
ConfuedProblemSolver

Reputation: 673

DocuSign Connect Webhook with .Net Core 3

I am creating a webhook in a .Net Core 3 Web API for DocuSign Connect to invoke and provide me status updates + signed documents from envelopes my app has created. The C# example at https://www.docusign.com/blog/dsdev-adding-webhooks-application was very helpful in getting me almost to my goal. The code from the example is:

[HttpPost("api/[controller]/ConnectWebHook")]
public void ConnectWebHook(HttpRequestMessage request)
{
    XmlDocument xmldoc = new XmlDocument();
    xmldoc.Load(request.Content.ReadAsStreamAsync().Result);

    var mgr = new XmlNamespaceManager(xmldoc.NameTable);
    mgr.AddNamespace("a", "http://www.docusign.net/API/3.0");

    XmlNode envelopeStatus = xmldoc.SelectSingleNode("//a:EnvelopeStatus", mgr);
    XmlNode envelopeId = envelopeStatus.SelectSingleNode("//a:EnvelopeID", mgr);
    XmlNode status = envelopeStatus.SelectSingleNode("./a:Status", mgr);

    var targetFileDirectory = @"\\my-network-share\";

    if (envelopeId != null)
    {
        System.IO.File.WriteAllText($"{targetFileDirectory}{envelopeId.InnerText}_{status.InnerText}_.xml", xmldoc.OuterXml);
    }

    if (status.InnerText == "Completed")
    {
        // Loop through the DocumentPDFs element, storing each document.

        XmlNode docs = xmldoc.SelectSingleNode("//a:DocumentPDFs", mgr);
        foreach (XmlNode doc in docs.ChildNodes)
        {
            string documentName = doc.ChildNodes[0].InnerText; // pdf.SelectSingleNode("//a:Name", mgr).InnerText;
            string documentId = doc.ChildNodes[2].InnerText; // pdf.SelectSingleNode("//a:DocumentID", mgr).InnerText;
            string byteStr = doc.ChildNodes[1].InnerText; // pdf.SelectSingleNode("//a:PDFBytes", mgr).InnerText;

            System.IO.File.WriteAllText($"{targetFileDirectory}{envelopeId.InnerText}_{documentId}_{documentName}", byteStr);
        }
    }
}

For testing purposes, my Web API is allowing all origins and exposed to the outside world via NGROK, and I can hit other test endpoints (both GET and POST), but for some reason this webhook is not being hit by Connect when there is a notification-worthy event on my envelope.

I can see in the DocuSign Admin portal logs that Connect invoked my webhook but got The remote server returned an error: (415) Unsupported Media Type.. This led me to add the [FromBody] attribute to my method signature like so but I still get the same error when my webhook is invoked by Connect.

[HttpPost("api/[controller]/ConnectWebHook")]
public void ConnectWebHook([FromBody] HttpRequestMessage request)
{
    // ... rest of the method was unchanged, removed for brevity
}

I have never used HttpRequestMessage before but it looks straightforward enough. I noticed in the DocuSign Admin portal logs that the data that Connect tried to send to the webhook is just XML. I could try to change the webhook's signature to look for an XmlDocument instead of an HttpRequestMessage but I am not sure what, if anything, I will be missing out on.

Has anyone else integrated with Connect via a webhook recently? And were you able to make the HttpRequestMessage work for you?

Added on 10/18/2019:

DocuSign mentions that the content type is XML. Here is what the content looks like:

<DocuSignEnvelopeInformation 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns="http://www.docusign.net/API/3.0">
  <EnvelopeStatus>...</EnvelopeStatus>
  <DocumentPDFs>...</DocumentPDFs>
</DocuSignEnvelopeInformation>

I have added AddXmlSerializerFormatters() to the ConfigureServices method in Startup.cs. This being .Net Core 3, I had to set it up like services.AddControllers().AddXmlSerializerFormatters() instead of services.AddMVC().AddXmlSerializerFormatters() per https://learn.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.0&tabs=visual-studio.

With that change, I have now tried using [FromForm] like so and my webhook IS being hit, but the request input parameter is essentially empty ... request.Content = null:

[HttpPost("api/[controller]/ConnectWebHook")]
public void ConnectWebHook([FromForm] HttpRequestMessage request)
{
    // ... rest of the method was unchanged, removed for brevity
}

Since the request is being sent from DocuSign Connect, I have no control over the headers/format/content. As far as I can tell, they are not submitting an XML object, not a form, so [FromForm] is probably not the way to go.

Upvotes: 4

Views: 3133

Answers (1)

Nkosi
Nkosi

Reputation: 247591

That linked example is not for .net core. HttpRequestMessage is no longer a first class citizen in asp.net-core framework and will treated as a normal model.

Just extract the content directly from the Request's body and the rest should be able to remain the same as in the example.

[HttpPost("api/[controller]/ConnectWebHook")]
public IActionResult ConnectWebHook() {

    Stream stream = Request.Body;

    XmlDocument xmldoc = new XmlDocument();
    xmldoc.Load(stream);

    var mgr = new XmlNamespaceManager(xmldoc.NameTable);
    mgr.AddNamespace("a", "http://www.docusign.net/API/3.0");

    XmlNode envelopeStatus = xmldoc.SelectSingleNode("//a:EnvelopeStatus", mgr);
    XmlNode envelopeId = envelopeStatus.SelectSingleNode("//a:EnvelopeID", mgr);
    XmlNode status = envelopeStatus.SelectSingleNode("./a:Status", mgr);

    var targetFileDirectory = @"\\my-network-share\";

    if (envelopeId != null) {
        System.IO.File.WriteAllText($"{targetFileDirectory}{envelopeId.InnerText}_{status.InnerText}_.xml", xmldoc.OuterXml);
    }

    if (status.InnerText == "Completed") {
        // Loop through the DocumentPDFs element, storing each document.

        XmlNode docs = xmldoc.SelectSingleNode("//a:DocumentPDFs", mgr);
        foreach (XmlNode doc in docs.ChildNodes) {
            string documentName = doc.ChildNodes[0].InnerText; // pdf.SelectSingleNode("//a:Name", mgr).InnerText;
            string documentId = doc.ChildNodes[2].InnerText; // pdf.SelectSingleNode("//a:DocumentID", mgr).InnerText;
            string byteStr = doc.ChildNodes[1].InnerText; // pdf.SelectSingleNode("//a:PDFBytes", mgr).InnerText;

            System.IO.File.WriteAllText($"{targetFileDirectory}{envelopeId.InnerText}_{documentId}_{documentName}", byteStr);
        }
    }

    return Ok();
}

Upvotes: 1

Related Questions