acumatica

Response of webhook is empty


We are using a webhook in our plugin to send a URL to customers for online approval. Upon clicking the URL, the page loads, accepts a signature, and attaches it to the relevant document.

This is a simple sample code I have used for testing in Acumatica 24R2 and the response is empty

public class TestWebhook : IWebhookHandler 

{ private NameValueCollection _queryParameters;

private const string cLocationCD = "KeyValues";
private const string cMode = "Mode";
private const string cSendMode = "SendNotification";
private const string cGetMode = "ReceiveResponse";
//async Task<IHttpActionResult> IWebhookHandler.ProcessRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
public async Task HandleAsync(WebhookContext context, CancellationToken cancellation)
{
    using (var scope = GetUserScope())
    {
        //_queryParameters = HttpUtility.ParseQueryString(request.RequestUri.Query);
        //_queryParameters = HttpUtility.ParseQueryString(context.Request.Query.ToString());

        string htmlResponse = string.Empty;
        if (!context.Request.Query.ContainsKey(cLocationCD))
            throw new Exception($"The {cLocationCD} Parameter was not specified in the Query String");
        IReadOnlyDictionary<string, Microsoft.Extensions.Primitives.StringValues> keycollection = context.Request.Query;
        var collectorID = context.Request.Query[cLocationCD];
        try
        {
            string sMode = string.Empty;
            string values = collectorID;
            string[] arr = values.Split(new char[] { ';' });
            string trantype = arr[0];
            string doctype = arr[1];
            string docnbr = arr[2];
            if (!keycollection.ContainsKey(cMode))
            {
                //if nothing is specified for a mode Parameter we will assume GetSurvey
                sMode = "SendNotification";
            }
            else
            {
                sMode = context.Request.Query[cMode];
            }

            switch (sMode)
            {
                case cSendMode:
                    htmlResponse = GetSendAuth(trantype, doctype, docnbr);
                    break;
                case cGetMode:
                    htmlResponse = SubmitApproval(collectorID, context.Response);
                    break;
                default:
                    //htmlResponse = ReturnModeNotRecognized(sMode);
                    break;
            }
        }
        catch (PXException e)
        {
            throw e;
        }
        StreamWriter writer = new StreamWriter(context.Response.Body);

        writer.Write(htmlResponse);

        writer.Close();
        //HtmlActionResult htmlaction = new HtmlActionResult(htmlResponse);

        //return htmlac;
    }
}
private IDisposable GetUserScope()
{

    //todo: For now we will use admin but we will want to throttle back to a 
    //      user with restricted access as to reduce any risk of attack.
    //      perhaps this can be configured in the Surveys Preferences/Setup page.
    var userName = "admin";
    if (PXDatabase.Companies.Length > 0)
    {
        var company = PXAccess.GetCompanyName();
        if (string.IsNullOrEmpty(company))
        {
            company = PXDatabase.Companies[0];
        }
        userName = userName + "@" + company;
    }
    return new PXLoginScope(userName);
}
private string GetSendAuth(string trantype, string doctype, string docnbr)
{
    string _retval = String.Empty;
    WebHookListener g = PXGraph.CreateInstance<WebHookListener>();
    string listningEndPoint = g.GetWekHooksUrl();
    string encrypedval = $"{trantype};{doctype};{docnbr}";
    listningEndPoint = $"{listningEndPoint}?{cLocationCD}={encrypedval}&{cMode}={cGetMode}";
    Tuple<string, string, string> label = GetLabel(trantype, doctype, docnbr);
    Tuple<string, string, string> value = GetValues(trantype, doctype, docnbr);
    StringBuilder builder = new StringBuilder();
    builder.AppendLine("<!DOCTYPE html>");
    builder.AppendLine("<html xmlns=\"http://www.w3.org/1999/xhtml\">");
    builder.AppendLine("<head runat=\"server\">");
    builder.AppendLine("<title>WebHook Test</title>");
    builder.AppendLine("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\" />");
    builder.AppendLine("</head>");
    builder.AppendLine("<body>");
    builder.AppendLine("<header><h2>Webhook Test</h2></header>");
    builder.AppendLine("<nav class=\"labels\">");
    builder.AppendLine($"<label>{label.Item1} :</label> <label>{value.Item1}</label> <br> <label>{label.Item2} :</label> <label>{value.Item2}</label>    <br> <label>{label.Item3} :</label><label>{value.Item3}</label> <br>");
    builder.AppendLine("</nav>");
    builder.AppendLine("<section>");
    builder.AppendLine($"<form id=\"form1\" runat=\"server\" class=\"sigPad\" style=\"width:100%; height:100%; position:relative; \" action= \"{listningEndPoint}\" method=\"post\" >");
    builder.AppendLine("<table width=\"100%\" cellpadding=\"2\" cellspacing=\"2\" border=\"0\">");
    builder.AppendLine("<tr><td><label for=\"myCheckbox\"><input type=\"checkbox\" id=\"cbAuthorize\" name=\"cbAuthorize\" runat=\"server\" />I agree and approve</label></td></tr>");
    builder.AppendLine("<tr><td><br><label for=\"name\"><b>Click button to submit</b></label><p><input type =\"submit\" value =\"Submit\"></p></td><td><div><img id=\"SigImg\" src=\"\" runat=\"server\" /></div></td></tr>");
    builder.AppendLine("</table>");
    builder.AppendLine("</form>");
    builder.AppendLine("</section>");
    builder.AppendLine("<footer>WebHook Test</footer>");
    builder.AppendLine("</body>");
    builder.AppendLine("</html>");
    _retval = builder.ToString();
    return _retval;
}

//private string SubmitApproval(string collectorID, HttpRequestMessage request)
private string SubmitApproval(string collectorID, PX.Api.Webhooks.WebhookResponse request)
{
    string body = string.Empty;
    Stream stream = request.Body;
    stream.Position = 0;
    using (StreamReader reader = new StreamReader(stream))
    {
        // Read the entire stream to a string
        body = reader.ReadToEnd();
    }
    string values = collectorID;
    string[] arr = values.Split(new char[] { ';' });
    string trantype = arr[0];
    string doctype = arr[1];
    string docnbr = arr[2];
    bool authorize = false;
    arr = body.Split(new char[] { '&' });
    foreach (string node in arr)
    {
        string[] element = node.Split(new char[] { '=' });
        switch (element[0].ToLower())
        {
            case "cbAuthorize": authorize = (element[1].ToLower() == "on"); break;
            default: break;
        }
    }
    SOOrderEntry g = PXGraph.CreateInstance<SOOrderEntry>();
    SOOrder ord = SOOrder.PK.Find(g, doctype, docnbr);
    if(ord != null)
    {
        WHSOOrderExt ext = ord.GetExtension<WHSOOrderExt>();
        ext.UsrApprove = authorize;
        ext.UsrHits = (ext.UsrHits ?? 0) + 1;
        g.Document.Update(ord);
        g.Save.Press();
    }
    var view = @"
           <!DOCTYPE html>
            <html>
            <body>
               <h1>Approval</h1>
              Thank You Your Submitted your answer was {0}
            </body>
       </html>
           ";
    return string.Format(view,((authorize)?"Yes":"No"));
}
private Tuple<string, string, string> GetLabel(string trantype, string doctype, string docnbr)
{
    Tuple<string, string, string> _retval = new Tuple<string, string, string>("", "", "");
    _retval = new Tuple<string, string, string>("Shipment #   : ",
                                                "Customer Name: ",
                                                "Contact #    : ");
    return _retval;
}

private Tuple<string, string, string> GetValues(string trantype, string doctype, string docnbr)
{
    Tuple<string, string, string> _retval = new Tuple<string, string, string>("", "", "");
    string[] val = GetSalesOrderLabelDetail(doctype, docnbr);
    _retval = new Tuple<string, string, string>(val[0], val[1], val[2]);
    return _retval;
}
private string[] GetSalesOrderLabelDetail(string type, string refnbr)
{
    string[] _return = new string[] { "", "", "" };
    SOOrderEntry grp = PXGraph.CreateInstance<SOOrderEntry>();
    grp.Document.Current = PXSelect<SOOrder, Where<SOOrder.orderType, Equal<Required<SOOrder.orderType>>, And<SOOrder.orderNbr, Equal<Required<SOOrder.orderNbr>>>>>.Select(grp, type, refnbr);
    if (grp.Document.Current != null)
    {
        List<string> arr = new List<string>();
        arr.Add(grp.Document.Current.OrderNbr);
        if (grp.customer.Current != null)
            arr.Add($"{grp.customer.Current.AcctCD}-{grp.customer.Current.AcctName}");
        else
            arr.Add(" ");
        if (grp.customer.Current != null)
        {
            Contact contact = PXSelect<Contact, Where<Contact.bAccountID, Equal<Required<Contact.bAccountID>>, And<Contact.contactID, Equal<Required<Contact.contactID>>>>>.Select(grp, grp.customer.Current.BAccountID, grp.customer.Current.DefBillContactID);

            if (contact != null)
                arr.Add(contact.Phone1 ?? string.Empty);
            else
                arr.Add(" ");
        }
        _return = arr.ToArray();
    }
    return _return;
}

}

The debugging code give the following error.

enter image description here


Solution

  • I raised a support case with Acumatica and received a solution to fix the issue.

    Acumatica recommended passing webhookRequest instead of webhookResponse in SubmitApproval to achieve the desired result.

    **private string SubmitApproval(string collectorID, PX.Api.Webhooks.WebhookRequest request)**
    {
        string body = string.Empty;
        Stream stream = request.Body;
        stream.Position = 0;
        using (StreamReader reader = new StreamReader(stream))
        {
            // Read the entire stream to a string
            body = reader.ReadToEnd();
        }
        string values = collectorID;
        string[] arr = values.Split(new char[] { ';' });
        string trantype = arr[0];
        string doctype = arr[1];
        string docnbr = arr[2];
        bool authorize = false;
        arr = body.Split(new char[] { '&' });
        foreach (string node in arr)
        {
            string[] element = node.Split(new char[] { '=' });
            switch (element[0].ToLower())
            {
                case "cbauthorize": authorize = (element[1].ToLower() == "on"); break;
                default: break;
            }
        }
        SOOrderEntry g = PXGraph.CreateInstance<SOOrderEntry>();
        SOOrder ord = SOOrder.PK.Find(g, doctype, docnbr);
        if(ord != null)
        {
            WHSOOrderExt ext = ord.GetExtension<WHSOOrderExt>();
            ext.UsrApprove = authorize;
            ext.UsrHits = (ext.UsrHits ?? 0) + 1;
            g.Document.Update(ord);
            g.Save.Press();
        }
        var view = @"
               <!DOCTYPE html>
                <html>
                <body>
                   <h1>Approval</h1>
                  Thank You Your Submitted your answer was {0}
                </body>
           </html>
               ";
        return string.Format(view,((authorize)?"Yes":"No"));
    }