DEV Community

Explorer
Explorer

Posted on

Handling Asynchronous Webhook Notifications & Callbacks in Joget via BeanShell

When integrating Joget DX with external platforms—such as payment gateways, SMS providers, or ERP systems—requests are often processed asynchronously. The external system accepts a request immediately and dispatches an HTTP POST webhook callback to Joget minutes or hours later when processing completes.

Receiving webhook callbacks inside BeanShell API endpoints requires two key tasks:

  1. Safe Variable Type Coercion: Handling parameter arrays (String[]) versus single strings (String) safely without throwing ClassCastException.
  2. FormDataDao Persistence: Saving or updating the notification payload inside a Joget form database table using FormDataDao.

In this guide, we'll write a defensive Java/BeanShell script that receives asynchronous webhook callbacks and logs them cleanly into Joget.


Architecture Overview

  1. Webhook Endpoint: An external system hits your Joget API endpoint with callback parameters (e.g. process_id, status, response_payload, recipient).
  2. Type Extraction: A safe helper function handles parameter type variations (whether passed via URL query params or JSON request bodies).
  3. FormDataDao Save: Instead of executing raw JDBC queries, the script uses FormDataDao to persist a FormRowSet directly into Joget's form storage engine.

The BeanShell Script

Place this code inside your API Builder BeanShell script or custom REST endpoint:

import org.joget.apps.app.service.AppUtil;
import org.joget.apps.form.dao.FormDataDao;
import org.joget.apps.form.model.FormRow;
import org.joget.apps.form.model.FormRowSet;
import org.joget.commons.util.LogUtil;
import java.util.UUID;

// 1. Safe Type Extraction Helper
public String safeExtract(Object param) {
    if (param == null) return "";
    try {
        if (param instanceof String[]) {
            String[] arr = (String[]) param;
            return arr.length > 0 ? arr[0] : "";
        }
        if (param instanceof String) {
            return (String) param;
        }
    } catch (Throwable t) {
        LogUtil.error("Webhook Receiver", t, "Error extracting parameter");
    }
    return "";
}

// 2. Extract Webhook Callback Parameters Safely
String processId    = safeExtract(request.getParameter("process_id"));
String recordId     = safeExtract(request.getParameter("record_id"));
String statusValue  = safeExtract(request.getParameter("status"));
String recipient    = safeExtract(request.getParameter("recipient"));
String responseMsg  = safeExtract(request.getParameter("response_message"));
String moduleName   = safeExtract(request.getParameter("module"));

LogUtil.info("Webhook Receiver", "Received callback for processId: " + processId + " | Status: " + statusValue);

// 3. Persist Log using Joget FormDataDao API
try {
    FormDataDao formDataDao = (FormDataDao) AppUtil.getApplicationContext().getBean("formDataDao");

    String formDefId = "notification_log"; // Joget Form ID
    String tableName = "app_fd_notification_log"; // Database Table Name

    FormRow row = new FormRow();
    row.setId(UUID.randomUUID().toString()); // Generate primary key
    row.setProperty("c_process_id", processId);
    row.setProperty("c_record_id", recordId);
    row.setProperty("c_status", statusValue);
    row.setProperty("c_recipient", recipient);
    row.setProperty("c_response_message", responseMsg);
    row.setProperty("c_module", moduleName);

    FormRowSet rowSet = new FormRowSet();
    rowSet.add(row);

    // Save record to Joget database
    formDataDao.saveOrUpdate(formDefId, tableName, rowSet);
    LogUtil.info("Webhook Receiver", "Successfully saved callback notification log.");

} catch (Exception e) {
    LogUtil.error("Webhook Receiver", e, "Error persisting webhook callback log");
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Use FormDataDao for Integrity: Utilizing FormDataDao.saveOrUpdate() automatically updates Joget's internal metadata columns (dateCreated, dateModified) and triggers active plugins.
  • Always Validate Inputs: Ensure process_id or record_id is validated to prevent invalid records from polluting your notification logs.

Top comments (0)