Can webhooks be triggered consecutively on an object?

Hi all,

I have a use case where a user might need to change the status of an item back and forth. In this screenshot, clicking save triggers a webhook on this selected item
image

I noticed that when I change the status to On Hold the first time, it does indeed trigger the webhook. However, when I change the status to Pending a few minutes after, it doesn’t trigger the webhook. I don’t even see anything queued up.

I took a look at the audit logs and I can see that the webhook flag (do_submit) isn’t even being set
image

Here’s the first webhook trigger:
image

Is there some timeout that’s occurring in between me triggering the webhook? Are there any workarounds for this?

Thanks,

Alex

@alex.dang are you using a ‘ready’ or an ‘update’ type webhook? Can you share the logic that sets the do_submit flag?

I’m using the ‘update’ type webhook.

Here’s the method that sets the do_submit flag:

/**
 * @param {any} view
 * @param {DB.booking_request | DB.collection | DB.item | DB.shipment} submit_obj
 * @param {string} state_id
 */
export async function doSubmit(view, submit_obj : DB.booking_request | DB.collection | DB.item | DB.shipment, state_id : string) {
    var clog = "Calling doSubmit\n";
    var isSubmit = true;

    var vemObject = {};

    try {
        if (submit_obj.submit_state === null) {
            clog += `${submit_obj.type.name}.submit_state was null -> continuing\n`;
            if (state_id != null) {
                submit_obj.state_id = state_id;
                submit_obj.submit_state = state_id;
                await submit_obj.save();                
            }

            if (submit_obj.type.name == "booking_request") {
                vemObject = await initializeBookingRequest(view, submit_obj);
            } else if (submit_obj.type.name == "collection") {
                vemObject = await initializeCollection(view, submit_obj);
            } else if (submit_obj.type.name == "item") {
                vemObject = await initializeItem(view, submit_obj);
            } else if (submit_obj.type.name == "shipment") {
                vemObject = await initializeShipment(view, submit_obj);
            } else {
                isSubmit = false;
                notification.error("Cannot submit unknown object.");
            }
        } else {
            clog += `${submit_obj.type.name}.submit_state was not null -> stopping\n`;
            isSubmit = false;
            notification.error(constants.SUBMIT_STATE_NOT_NULL_ERROR);
        }

        if (isSubmit) {
            clog += `vemObject: ${JSON.stringify(vemObject)}\n`;

            submit_obj.submit_data = await Attachment.create({
                text: JSON.stringify(vemObject),
                filename: 'data.txt'
            });

            submit_obj.submitted_at = new Day();
            submit_obj.submitted_at_time = new Date();
            await submit_obj.submitted_by(user);
            submit_obj.do_submit = true;
            await submit_obj.save();
            
            clog += "submit_obj.do_submit is now: " + submit_obj.do_submit + "\n";
        }
    } catch (e) {
        isSubmit = false;
        clog += "Caught Error: " + e.message + "\n";
    }

    console.log(clog);
    return isSubmit;
}

And then here’s the relevant method being called in the submit_obj.type.name condition check:

/**
 * @param {any} view
 * @param {DB.item} item
 * @returns vemObject
 */
async function initializeItem(view, item) {
    let data : any = {};
    let surveyObjects = [];
    let vemObject = null;
    
    if (item.is_change_location_status) {
        console.log("Calling change location / status");
        item.is_change_location_status = null;
        data.location = item.location;        
        data.state_id = item.submit_state;
        item.submit_state = null;        
        await item.save();
        
        vemObject = Object.assign({
            id: item.id,
            type: item.type.name,
            surveyObjects: surveyObjects,
        }, data);
        vemObject.data = "REDACTED";            
    } else {
        console.log("Calling new item creation")
        data.state_id = item.submit_state;
        item.submit_state = null;
        await item.save();

        data.booking_request_id = item.booking_request_id;
        data.collected_by_id = item.collected_by_id;
        
        vemObject = Object.assign({
            id: item.id,
            type: item.type.name,
            surveyObjects: surveyObjects,
        }, data, item);
        vemObject.data = "REDACTED";                
    }
    return vemObject;
}

Hi @alex.dang

I think the issue is stemming from the usage of DB. (synced data) vs CloudCode (online data).

My theory is that the first time do_submit is set, it saves, syncs up and then fires the webhook as expected.
Secondly, in this case, when the second do_submit is set in the app, the synced DB. has not yet updated with the change CloudCode made, namely do_submit = null;, so the synced DB. save() still sees do_submit === true and so it does not see the second assignment as an update to the object because the value is still the same in the synced DB.. In other words, the second assignment of do_submit is seen as a “no-change” because it is setting do_submit to true while it was still true.

I would suggest having a check in the app to see if there is currently a “do_submit” in-progress with an OnlineDB call and instruct the user to wait a minute before firing another change. If offline flow is expected, then perhaps implement a dedicated object that can be created for each “do_submit” action, this way they will all get processed when the user comes online.

Let me know if any of the above is unclear or if you have any further questions.

Thanks Willem! We ended up staying with this method. The user would leave the item On Hold for days or hours so this should be fine. I’ll keep this in mind for any future implementations though.