• What is webhook?

    Webhooks are a useful tool for applications that want to execute code after a specific event happens, for example, after an purchase order is created, or an return is confirmed, or an order is shipped.

    You can use webhook subscriptions to receive notifications about particular events.

    Instead of telling your app to make an API call every X number of minutes to check if a specific event has occured on a shop, you can subscribe to webhooks. When the event you have registered for occurs, the Moduslink API portal will trigger a notification to your application.

    This will reduce the number of API calls your system has to make, allowing you to build more efficient apps, and update your app instantly after a webhook is received.

    Webhook events you can subscribe to:

    • Sales Order
      • Created
      • Confirmed
      • Cancelled
      • Released
      • Reserved
      • Partially Shipped
      • Shipped
      • Invoiced
      • Paid
    • Returns
      • Created
      • Confirmed
      • Cancelled
      • Partially Returned
      • Returned
    • Purchase Order
      • Received
      • Confirmed
      • Cancelled
      • Goods Partialy Received
      • Goods Received

    For more information about webhooks refer to the other sections related to webHooks.

  • Configure a webhook

    As part of the onboarding process, ModusLink will configure the applicable WebHook events. The configured events can be changed at any time depending on the requirements or needs.

    You will need to provide the endpoint(s) to receive the WebHook message to Moduslink so we can have the endpoint(s) setup as part of the configuration.

  • Receive a webhook

    Once you register a webhook URL with ModusLink, we will issue a HTTPS POST request to the configured URL every time that event occurs. The request's POST message will contain JSON data relevant to the event that triggered the request.

    The message will have the following information:

    Sample Order WebHook message:

    {
      "eventType":"Orders.Created",
      "eventObjectId":"42000631",
      "eventObjectName":"orders",
      "eventObjectReference":"Your_ref_60",
      "eventDateTime":"2019-03-27T14:58:03",
      "links":[
        {
          "href":"/fulfillmentapi/v1.2/orders/42000631",
          "rel":"orders"
        }
      ]
    }

    Sample Return WebHook message:

    {
      "eventType":"Rmas.Created",
      "eventObjectId":"42000631",
      "eventObjectName":"rmas",
      "eventObjectReference":"Your_ref_60",
      "eventDateTime":"2019-03-27T14:58:03",
      "links":[
        {
          "href":"/fulfillmentapi/v1.2/rmas/42000631",
          "rel":"rmas"
        }
      ]
    }

    Sample PO WebHook message:

    {
      "eventType":"PO.Confirmed",
      "eventObjectId":"42000631",
      "eventObjectName":"purchaseorders",
      "eventObjectReference":"Your_ref_60",
      "eventDateTime":"2019-03-27T14:58:03",
      "links":[
        {
          "href":"/fulfillmentapi/v1/orders/42000631",
          "rel":"purchaseorders"
        }
      ]
    }

    Below table describes the relevant fields in the webHook message:

    Element Description
    eventType  The event that was triggered
    eventObjectId  RequestId of the event trigger
    RequestId  of the event triggered
    eventObjectName  Type of object e.g. Order, Purchase Order or Return
    eventObjectReference  Your reference
    eventDateTime  Date-timestamp of the event
    Links  Link to the object

    Please note standard each webhook will trigger once, if successfully received. It is however possible a webhook will trigger multiple times, or skips a intermediate status, as due to the asynchronous nature of our webhook queue processing a status can be 'overtaken' by a subsequent status if they did happen in close proximity.

  • Respond to webhook

    Your application acknowledges that it received the WebHook by sending a 200 OK response. Any response outside of the 200 range will be handled by ModusLink as confirmation that you did not receive our webhook. Moduslink does not follow redirects for webhook notifications and will consider a redirection as an error response.

    For every webhook connection error, an automatic retry is attempted 5 times with 5 minute intervals. After that it will be dropped from the queue.

    If you're receiving a ModusLink webhook, it is important that your application responds quickly.

  • Verify a webhook created by ModusLink

    Webhooks created by ModusLink API can be verified by calculating a digital signature.

    Each webhook request includes a base64-encoded X-Moduslink-HMAC-SHA256 header, which is generated using the app's shared secret along with the data sent in the request.

    To verify that the request came from ModusLink, compute the HMAC digest value and compare it to the value in the X-Moduslink-HMAC-SHA256 header. If they match, you can be sure that the Webhook was sent from ModusLink and the data has not been compromised.

    The examples below demonstrate how to verify a WebHook request in different languages, for example: C# using the Microsoft.NET framework.

                      
    public static class PostWebhook
        {
            //The below function will process the incoming WebHook request and return   HttpStatusCode'OK' if it is sucessfull 
            [FunctionName("ProcessWebhook")]
            public static async  Task<HttpResponseMessage> ProcessWebhook([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
            {
                HttpResponseMessage response = new HttpResponseMessage();
                try
                {
                    log.Info("C# HTTP trigger function processed a request.");
                    //Get request data
                    string requestData = new StreamReader(req.Content.ReadAsStreamAsync().Result).ReadToEnd().ToString().Trim();
                    if (requestData != "")
                    {
                        IEnumerable<string> headerValues = req.Headers.GetValues("X-Moduslink-HMAC-SHA256");
    
                        log.Info($"Application XModuslinkHMACSHA256 value: {headerValues.FirstOrDefault()} ");
                        log.Info($"WebhookInfo Message: {requestData} ");
    
                        //Get the SecretKey value from the configuration settings
                        string SecretKey = System.Environment.GetEnvironmentVariable("X-Moduslink-HMAC-SHA256_Key");
    
                        WebhookInformation objWebhook = new WebhookInformation();
                        objWebhook = JsonConvert.DeserializeObject<WebhookInformation>(requestData);
    
                        //Varify whether the computed HMAC value  and the HMAC value in the request Header are matching 
                        if (VarifyHMACValue(headerValues.FirstOrDefault().Trim(), SecretKey, objWebhook, log) == true)
                        {
                            log.Info($"XModuslinkHMACSHA256 value: {headerValues.FirstOrDefault()} is matching ");
                            string webhookinfoJson = JsonConvert.SerializeObject(objWebhook);
                            log.Info($"Message Received.: {webhookinfoJson} ");
                            response = req.CreateResponse(HttpStatusCode.OK, $"WebhookInfo processed successfully");
                        }
                        else
                        {
                            log.Info($"XModuslinkHMACSHA256 value: {headerValues.FirstOrDefault()} is NOT matching, further processes aborted. ");
                            response = req.CreateResponse(HttpStatusCode.OK, $"WebhookInfo not processed due to the mismtach in XModuslinkHMACSHA256 value");
                        }
                    }
                    else
                        response = req.CreateResponse(HttpStatusCode.BadRequest, $"Please pass a name on the query string or in the request body");
                }
                catch (Exception ex)
                {
                    log.Info($"Error occured: {ex.Message} ");
                    response = req.CreateResponse(ex);
                }
                return await Task.FromResult(response);
            }
    
            //The below function will calculate the HMAC value by using the secret key provided and compare it with the HMAC value available in the request header 
            public static bool VarifyHMACValue(string ModuslinkHMACValue,string secretKey, WebhookInformation requestData, TraceWriter log)
            {
                string HMAHeader = ModuslinkHMACValue;
                string CalculatedHMAC = null;
                HMACSHA256 HMAC = new HMACSHA256(Encoding.UTF8.GetBytes(secretKey.Trim()));
                byte[] Hash = HMAC.ComputeHash(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(requestData)));
                CalculatedHMAC = Convert.ToBase64String(Hash);
                //remove the padding character from the CalculatedHMAC
                CalculatedHMAC = CalculatedHMAC.Remove(CalculatedHMAC.Length - 1);
                //Now verify that the actual hash value in the request header is matching with the calucalted hash value.
                if (HMAHeader == CalculatedHMAC)
                {
                    //Do something
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }
    
        public class WebhookInformation
        {
            public WebhookInformation()
            {
                links = new List<Link>();
            }
            public string eventType { get; set; }
            public string eventObjectId { get; set; }
            public string eventObjectName { get; set; }
            public string eventObjectReference { get; set; }
            public string eventDateTime { get; set; }
            public List<Link> links { get; set; }
        }
        public class Link
        {
            public string href { get; set; }
            public string rel { get; set; }
        }
    
    
    
    
       
    public class Function {
        //The below function will process the incoming WebHook request and return   HttpStatusCode'OK' if it is sucessfull
        @FunctionName("ProcessWebhook")
        public HttpResponseMessage run(
                @HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.FUNCTION) HttpRequestMessage> request,
                final ExecutionContext context) {
            context.getLogger().info("Java HTTP trigger processed a request.");
    
            // Parse query parameter
            String query = request.getQueryParameters().get("name");
            String name = request.getBody().orElse(query);
            //Get the  ModusLink HMAC values from request Header
            String strHeaderValue = request.getHeaders().get("x-moduslink-hmac-sha256");
            //Get the SecretKey value from the configuration settings
            String SecretKey = System.getenv("X-Moduslink-HMAC-SHA256");
    
            WebhookInformation webhookInfo = null;
            try{
                webhookInfo = (WebhookInformation) new ObjectMapper().readValue(name, WebhookInformation.class);
                //Varify whether the computed HMAC value  and the HMAC value in the request Header are matching
                if (VarifyHMACValue(strHeaderValue.trim(), SecretKey, webhookInfo) == true)
                {
                    String webhookinfoJson = new ObjectMapper().writeValueAsString(webhookInfo);
                    return request.createResponseBuilder(HttpStatus.OK).body("WebhookInfo processed successfully").build();
                }
          else
                {
                    return request.createResponseBuilder(HttpStatus.BAD_REQUEST).body("WebhookInfo not processed due to the mismtach in XModuslinkHMACSHA256 value").build();
                }
    
            }catch(IOException io) {
                io.printStackTrace();
            } 
        }
        //The below function will calculate the HMAC value by using the secret key provided and compare it with the HMAC value available in the request header
        private static boolean VarifyHMACValue(String ModuslinkHMACValue, String secretAccessKey, WebhookInformation requestData)
            {
                try
                {
                    String data = new ObjectMapper().writeValueAsString(requestData);
                    byte[] secretKey = secretAccessKey.getBytes();
                    SecretKeySpec signingKey = new SecretKeySpec(secretKey, "HmacSHA256");
                    Mac mac = Mac.getInstance("HmacSHA256");
                    mac.init(signingKey);
                    byte[] bytes = data.getBytes("utf-8");
                    byte[] rawHmac = mac.doFinal(bytes);
                    String CalculatedHMAC = Base64.getEncoder().encodeToString(rawHmac);
                    
                    if(CalculatedHMAC.contains("="))
                    {
                        CalculatedHMAC = CalculatedHMAC.substring(0, CalculatedHMAC.length() -1);
                    }
                    //Now verify that the actual hash value in the request header is matching with the calucalted hash value.
                    if (ModuslinkHMACValue.equals(CalculatedHMAC))
                    {
                        //Do something 
                        return true;
                    }
                    else
                    {
                        return false;
                    } 
                }
                catch(Exception ex)
                {
    
                }
                return false;
            }
    }
    
    class WebhookInformation
        {
            private String eventType;
            public String getEventType() {
                return this.eventType;
            }
            public void setEventType(String eventType) {
                this.eventType = eventType;
            }
            
            private String eventObjectId ;
            public String getEventObjectId() {
                return this.eventObjectId;
            }
            public void setEventObjectId(String eventObjectId) {
                this.eventObjectId = eventObjectId;
            }
    
            private String eventObjectName;
            public String getEventObjectName() {
                return this.eventObjectName;
            }
            public void setEventObjectName(String eventObjectName) {
                this.eventObjectName = eventObjectName;
            }
    
            private String eventObjectReference;
            public String getEventObjectReference() {
                return this.eventObjectReference;
            }
            public void setEventObjectReference(String eventObjectReference) {
                this.eventObjectReference = eventObjectReference;
            }
    
            private String eventDateTime;
            public String getEventDateTime() {
                return this.eventDateTime;
            }
            public void setEventDateTime(String eventDateTime) {
                this.eventDateTime = eventDateTime;
            }
    
            private List links;
            public List getLinks() {
                return this.links;
            }
            public void setLinks(List links) {
                this.links = links;
            }
        }
         class Link
        {
            private String href;
            public String getHref() {
                return this.href;
            }
            public void setHref(String href) {
                this.href = href;
            }
            private String rel;
            public String getRel() {
                return this.rel;
            }
            public void setRel(String rel) {
                this.rel = rel;
            }
        }
    
    
  • webhook events by Type

    The Client will register a webhook endpoint as per our specifications where Moduslink will post trigger events to notify when there is a change in order processing. The webhook event will be a light weight message conveying the actual event, order reference and information to construct the unique URL of Moduslink status check API to fetch the complete order status. Moduslink clients will have the flexibility to subscribe to the events they are interested to be notified using this webhook mechanism.

    The table below provides a comprehensive list of identified events and their description.

    Event Type   Description
    Orders.Received   Sales Order Receipt– valid message received – "created"
    Orders.Confirmed   Sales Order Created triggered from SAP -> OMS -> API, "confirmed"
    Orders.Cancelled   Sales Order Cancelled / line cancelled "cancelled"
    Orders.Released   Sales Order Released Optional for Tier 1 clients Only: Order Released "released" –  release of FMS credit block
    Orders.Reserved   Stock Reserved Optional for Tier 1 clients Only: Sales Order Reserved "reserved"
    Orders.PartiallyShipped   Sales Order Shipped partially, with Carrier, ServiceCode, AWB, Serial Numbers, packages "partiallyshipped"
    Orders.Shipped   Sales Order Shipped, with Carrier, ServiceCode, AWB, Serial Numbers, packages "shipped"
    Orders.Invoiced   Optional – when FMS relevant:Sales Order Invoiced
    Orders.Paid   Optional – when FMS relevant:Sales Order Paid
         
    Rmas.Created   Return (RMA) Receipt - valid message received – "created"
    Rmas.Confirmed   Return (RMA) Created triggered from SAP -> OMS -> API, "confirmed"
    Rmas.Cancelled   Return (RMA) Cancelled "cancelled"
    Rmas.PartiallyReturned   Product Partially Received against the Return (RMA).
    Rmas.Returned   Product Received against the Return (RMA).
         
    PO.Received   Purchase Order Receipt– valid message received – "created"
    PO.Confirmed   Purchase Order Created triggered from SAP -> OMS -> API, "confirmed"
    PO.Cancelled   Purchase Order Cancelled / line cancelled "cancelled"
    PO.GoodsPartiallyReceived   Purchase order is partially received
    PO.GoodsReceived   Purchase order is received completly