triggering built-in webhook based on some condition  Topic is solved

Discussions relating to plugin development, and the Jiwa API.

triggering built-in webhook based on some condition

Postby sameermoin » Tue Nov 15, 2022 5:37 am

Hi Everyone,


I enabled the webhooks and subscribed for inventory.Updated event.

I have created one boolean custom field in the Inventory item and now I want to change the webhook behavior so that it will trigger only for those specific inventory items where that boolean field is true.

Is it achievable without creating a custom webhook event for Inventory.Updated


Regards,

Sameer
sameermoin
Regular Contributor
Regular Contributor
 
Posts: 75
Joined: Wed Jun 15, 2022 4:02 am
Topics Solved: 1

Re: triggering built-in webhook based on some condition

Postby SBarnes » Tue Nov 15, 2022 8:07 am

Whilst you can go into the REST API plugin and change the business logic of the inventory save end, this is something I wouldn't recommend you do as every time the REST api plugin gets updates to a new version you would need to go and make the same change.

You could try implementing a separate plugin and attach to the DTOSerialiseStart event and throw a client cancelled exception if your condition is not met but without trying it I don't know if this will work but I am pretty sure it should. The first argument in will be the sender i.e. the inventory object.

Other than that your best best option would be a custom hook or if you have control of the code that receives the hook check the custom fields there.
Regards
Stuart Barnes
SBarnes
Shihan
Shihan
 
Posts: 1619
Joined: Fri Aug 15, 2008 3:27 pm
Topics Solved: 175

Re: triggering built-in webhook based on some condition  Topic is solved

Postby Mike.Sheen » Tue Nov 15, 2022 12:06 pm

There is another way - Stuarts' suggestions will work, but I think a more elegant solution is to add a typed request filter for the internal post route clients send to the api (the WebhooksEventsPOSTRequest), and in there if your conditions are not satisfied then simply short circuit the request with a res.Close().

Attached is a plugin which does this. It Selectively filters webhooks for inventory.updated webhooks to only send the webhook if a custom field "SendUpdatedWebhook" is checked.

The entire code is below.

Code: Select all
public class CustomiseWebhook : System.MarshalByRefObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaRESTAPIPlugin
{
   public void Configure(JiwaFinancials.Jiwa.JiwaApplication.Plugin.Plugin Plugin, ServiceStack.ServiceStackHost AppHost, Funq.Container Container, JiwaFinancials.Jiwa.JiwaApplication.Manager JiwaApplicationManager)
    {
      AppHost.RegisterTypedRequestFilter<JiwaFinancials.Jiwa.JiwaServiceModel.WebhooksEventsPOSTRequest>((req, res, dto) => { WebhooksEventsPOSTRequestFilter(req, res, dto); });
   }
   
   public void WebhooksEventsPOSTRequestFilter(IRequest req, IResponse res, JiwaFinancials.Jiwa.JiwaServiceModel.WebhooksEventsPOSTRequest dto)
    {      
      if (dto.EventName == "inventory.updated")
      {         
         JiwaFinancials.Jiwa.JiwaServiceModel.Inventory.InventoryItem inventoryItem = dto.Body.FromJson<JiwaFinancials.Jiwa.JiwaServiceModel.Inventory.InventoryItem>();
         if (inventoryItem.CustomFieldValues.Count(x => x.SettingName == "SendUpdatedWebhook" && x.Contents == bool.TrueString) == 0)
         {
            res.Close();
         }
      }      
   }
}
Attachments
Plugin Filter Webhook.xml
(10.26 KiB) Downloaded 71 times
Mike Sheen
Chief Software Engineer
Jiwa Financials

If I do answer your question to your satisfaction, please mark it as the post solving the topic so others with the same issue can readily identify the solution
User avatar
Mike.Sheen
Overflow Error
Overflow Error
 
Posts: 2444
Joined: Tue Feb 12, 2008 11:12 am
Location: Perth, Republic of Western Australia
Topics Solved: 756

Re: triggering built-in webhook based on some condition

Postby sameermoin » Mon Dec 12, 2022 6:41 am

Is it possible to modify this webhook response inside this WebhooksEventsPOSTRequestFilter method ?
sameermoin
Regular Contributor
Regular Contributor
 
Posts: 75
Joined: Wed Jun 15, 2022 4:02 am
Topics Solved: 1

Re: triggering built-in webhook based on some condition

Postby SBarnes » Mon Dec 12, 2022 8:07 am

Define what you mean by change so Mike and I can best advise you, are you looking to reshape the Json or get at specific http parts?

You already have the response which is res in

Code: Select all
 WebhooksEventsPOSTRequestFilter(req, res, dto);


There is also RegisterTypedMessageResponseFilter

The documentation on filters is here https://docs.servicestack.net/request-a ... se-filters

But I am unsure as to why you would ever want to change the response on this particular route.
Regards
Stuart Barnes
SBarnes
Shihan
Shihan
 
Posts: 1619
Joined: Fri Aug 15, 2008 3:27 pm
Topics Solved: 175

Re: triggering built-in webhook based on some condition

Postby sameermoin » Mon Dec 12, 2022 4:20 pm

The problem is that I am calling 3-4 API calls on the other application when this webhook is triggered, to get my desired response I want to merge all of them into this webhook response. To do that I have created my own Inventory class with all the required properties that I need on the other ends.

It's already sending a JSON and instead of that, I am trying to send my own response.
sameermoin
Regular Contributor
Regular Contributor
 
Posts: 75
Joined: Wed Jun 15, 2022 4:02 am
Topics Solved: 1

Re: triggering built-in webhook based on some condition

Postby SBarnes » Mon Dec 12, 2022 4:45 pm

WebhooksEventsPOSTRequest returns void it is called regardless of the type of web hook, inventory, product etc, it is designed to fire from the business logic objects save end events and get web hooks into the queue, the hook system then takes over to do retries etc.

A web hook posts data out and the only response it is interested in is did the data get to the other end by getting a 200 ok back.

By what you are describing that you want to do you would be better to write a custom post route and perform a double hop namely have the web hook call your custom route that is a post on the api only once then have your route make its 3 - 4 calls, do what ever you want with the responses such update what ever and then simply send back a 200 ok if everything went ok otherwise send an error code like bad request so the hook system will retry if it needs to.

Below is the code of what the WebhooksEventsPOSTRequest does

Code: Select all
 public void Post(WebhooksEventsPOSTRequest request)
        {
            // This is the route invoked by clients to indicate an event has occurred.

            // Check key               
            string clientKey = this.Request.GetHeader("ClientKey");
            if (clientKey == null || clientKey != RESTAPIPlugin.ClientKey)
                throw new JiwaApplication.Exceptions.PermissionDeniedException("Invalid ClientKey provided.");

            // Add items to the message queue with status 0 (not sent)
            List<SY_WebhookSubscription> subscriptions = RESTAPIPlugin.WebhookSubscriptions.Where(x => x.EventName == request.EventName).ToList<SY_WebhookSubscription>();
            foreach (SY_WebhookSubscription subscription in subscriptions)
            {
                SY_WebhookMessage newMessage = new SY_WebhookMessage { RecID = System.Guid.NewGuid(), SY_WebhookSubscription_RecID = subscription.RecID, Body = request.Body, Status = 0, Retries = 0 };
                Db.ExecuteSql(@"INSERT INTO SY_WebhookMessage (RecID, SY_WebhookSubscription_RecID, Body, ItemNo, Status, LastSavedDateTime, AddedDateTime, Retries)
                              VALUES (@RecID, @SY_WebhookSubscription_RecID, @Body, (SELECT COALESCE(MAX(ItemNo), 0) + 1 FROM SY_WebhookMessage), @Status, SYSUTCDATETIME(), SYSUTCDATETIME(), @Retries)",
                              new { RecID = newMessage.RecID, SY_WebhookSubscription_RecID = newMessage.SY_WebhookSubscription_RecID, Body = newMessage.Body, Status = newMessage.Status, Retries = newMessage.Retries });

                // read back and keep that in our internal list as our item no and lastsaved date time is set at the database level.
                newMessage = Db.SingleById<SY_WebhookMessage>(newMessage.RecID);

                RESTAPIPlugin.WebhookMessages.Add(newMessage);
                // send to url         
                WebHookController.CallWebhook(newMessage);
            }
        }



Code: Select all
        static public void CallWebhook(SY_WebhookMessage message)
        {
            if (message.Status == 1)
                return;

            SY_WebhookSubscription subscription = RESTAPIPlugin.WebhookSubscriptions.Where(x => x.RecID == message.SY_WebhookSubscription_RecID).FirstOrDefault();
            List<SY_WebhookSubscriptionRequestHeader> subscriptionRequestHeaders = RESTAPIPlugin.WebhookSubscriptionRequestHeaders.Where(x => x.SY_WebhookSubscription_RecID == message.SY_WebhookSubscription_RecID).OrderBy(x => x.ItemNo).ToList<SY_WebhookSubscriptionRequestHeader>();

            int retries = message.Retries;

            if (message.Status != 0)
            {
                // Calculate the UTC time we need to be older than to continue
                int seconds = (int)((RESTAPIPlugin.RetryInterval) * Math.Pow(10, message.Retries));
                int differenceMilliseconds = (int)((TimeSpan)(DateTime.UtcNow - message.LastSavedDateTime)).TotalMilliseconds;

                if (differenceMilliseconds < (seconds * 1000))
                {
                    return;
                }

                retries++;
            }

            int newStatus = message.Status;
            SY_WebhookMessageResponse messageResponse = new SY_WebhookMessageResponse { RecID = System.Guid.NewGuid(), SY_WebhookMessage_RecID = message.RecID };

            try
            {
                // This is the actual POST to the webhook
                subscription.URL.PostStringToUrl(message.Body,
                                                 requestFilter: webRequest =>
                                                 {
                                                     foreach (SY_WebhookSubscriptionRequestHeader subscriptionRequestHeader in subscriptionRequestHeaders)
                                                     {
                                                         webRequest.Headers[subscriptionRequestHeader.Name] = subscriptionRequestHeader.Value;
                                                     }
                                                 });
                messageResponse.HTTPCode = 200;
                newStatus = 1;
            }
            catch (System.Exception ex)
            {
                while (ex.InnerException != null) ex = ex.InnerException;

                newStatus = 2;
                messageResponse.Message = ex.Message;
                int statusCode = 0;

                if (ex.GetStatus() != null)
                    statusCode = (int)ex.GetStatus();

                messageResponse.HTTPCode = statusCode;

                if ((statusCode >= 200 && statusCode < 300) || ex.IsAny300() || statusCode == 410)
                    newStatus = 3;  // Permanent fail - we do not attempt to retry these                     
            }
            finally
            {
                if (message.Retries >= RESTAPIPlugin.MaxRetries)
                    newStatus = 3;

                message.Status = (byte)newStatus;
                message.LastSavedDateTime = DateTime.UtcNow;
                message.Retries = retries;

                RESTAPIPlugin.AppHost.GetDbConnection().ExecuteSql(@"UPDATE SY_WebhookMessage
                              SET Status = @Status,
                              Retries = @Retries,
                              LastSavedDateTime = @LastSavedDateTime
                              WHERE RecID = @RecID",
                                  new { RecID = message.RecID, Status = message.Status, Retries = message.Retries, LastSavedDateTime = message.LastSavedDateTime });

                RESTAPIPlugin.AppHost.GetDbConnection().ExecuteSql(@"INSERT INTO SY_WebhookMessageResponse (RecID, SY_WebhookMessage_RecID, HTTPCode, Message, ItemNo, LastSavedDateTime)
                              VALUES (@RecID, @SY_WebhookMessage_RecID, @HTTPCode, @Message, (SELECT COALESCE(MAX(ItemNo), 0) + 1 FROM SY_WebhookMessageResponse WHERE SY_WebhookMessage_RecID = @SY_WebhookMessage_RecID), SYSUTCDATETIME())",
                              new { RecID = messageResponse.RecID, SY_WebhookMessage_RecID = message.RecID, HTTPCode = messageResponse.HTTPCode, Message = messageResponse.Message });

                if (message.Status == 1 || message.Status == 3)
                {
                    // remove from internal in-memory list
                    SY_WebhookMessage existingItem = RESTAPIPlugin.WebhookMessages.FirstOrDefault(x => x.RecID.ToString().ToLower() == message.RecID.ToString().ToLower());
                    if (existingItem != null)
                        RESTAPIPlugin.WebhookMessages.Remove(existingItem);
                }
            }
        }
Regards
Stuart Barnes
SBarnes
Shihan
Shihan
 
Posts: 1619
Joined: Fri Aug 15, 2008 3:27 pm
Topics Solved: 175

Re: triggering built-in webhook based on some condition

Postby Mike.Sheen » Mon Dec 12, 2022 5:09 pm

The original requirement was pretty simple:

I have created one boolean custom field in the Inventory item and now I want to change the webhook behavior so that it will trigger only for those specific inventory items where that boolean field is true.


And I believe you've been provided with a perfectly appropriate way of achieving that.

However, with the addition of this new information:

sameermoin wrote:The problem is that I am calling 3-4 API calls on the other application when this webhook is triggered, to get my desired response I want to merge all of them into this webhook response. To do that I have created my own Inventory class with all the required properties that I need on the other ends.

It's already sending a JSON and instead of that, I am trying to send my own response.


It seems to me to be a lot different to what you were originally asking, and so much so at the previous solution to your original question might not be able to be used or modified to satisfy thew new requirements. I actually don't even really understand what the new requirements are... It's beginning to smell very complex and REST principles work best with simple, not complex.

When you say " I am calling 3-4 API calls on the other application when this webhook is triggered" - are you making these calls inside the Jiwa API? Or are you making them in whoever receives the webhook coming out of Jiwa?
Mike Sheen
Chief Software Engineer
Jiwa Financials

If I do answer your question to your satisfaction, please mark it as the post solving the topic so others with the same issue can readily identify the solution
User avatar
Mike.Sheen
Overflow Error
Overflow Error
 
Posts: 2444
Joined: Tue Feb 12, 2008 11:12 am
Location: Perth, Republic of Western Australia
Topics Solved: 756

Re: triggering built-in webhook based on some condition

Postby sameermoin » Mon Dec 12, 2022 6:35 pm

I am storing some other product inventoryId inside the custom field. Now, when I am getting this webhook response inside my custom application. I am getting these product IDs from the CustomFieldValues section and then calling the GET inventory endpoint to get details of the rest of the products.

So, instead of calling the endpoint individually, I want to append these product details inside that webhook response.

The previous requirement that I asked for earlier is satisfied, but I think what I want to achieve can be implemented here, as this method is only deserializing the response into InventoryItem so, I think I can do customization here and then serialize it back into the response.


Regards,

Sameer
sameermoin
Regular Contributor
Regular Contributor
 
Posts: 75
Joined: Wed Jun 15, 2022 4:02 am
Topics Solved: 1

Re: triggering built-in webhook based on some condition

Postby Mike.Sheen » Mon Dec 12, 2022 6:51 pm

sameermoin wrote:I want to append these product details inside that webhook response.


I think we need to clear up some terminology here.

I think when you say response here, you mean request. It would make a lot more sense if you wanted to modify the request which is going to be sent out to the webhook recipient.

Modifying the response won't make a lot of sense, as the response of the outgoing webhook is at most merely logged, and if you're referring to the response to the internal POST made by clients to trigger a potential webhook event, then those responses are completely ignored.
Mike Sheen
Chief Software Engineer
Jiwa Financials

If I do answer your question to your satisfaction, please mark it as the post solving the topic so others with the same issue can readily identify the solution
User avatar
Mike.Sheen
Overflow Error
Overflow Error
 
Posts: 2444
Joined: Tue Feb 12, 2008 11:12 am
Location: Perth, Republic of Western Australia
Topics Solved: 756

Next

Return to Technical and or Programming

Who is online

Users browsing this forum: No registered users and 34 guests