Skip to content

Maker-Checker Approval Workflow ​

Maker-checker, also called the four-eyes principle, is an internal control in which one user (the maker) submits a sensitive action and a different user (the checker) must approve it before it takes effect. In Apache Fineract, submitted actions wait in a pending queue until an authorized checker approves or rejects them.


How It Works ​

  1. A maker initiates an action - for example, approving a loan
  2. Instead of executing immediately, the action is placed in a pending queue
  3. A checker (a different user with the appropriate permission) reviews the pending action
  4. The checker approves or rejects it
  5. On approval, the action executes; on rejection, it is not executed and the entry is kept in the audit log with a rejected status

By default the maker and checker must be different users: Fineract refuses to approve, reject or delete a pending action when the requester is the user who submitted it ("Can not be checked by the same user."). This check is skipped when the enable-same-maker-checker global configuration is enabled (it is disabled by default), and for users who hold CHECKER_SUPER_USER or ALL_FUNCTIONS.


Enabling Maker-Checker Globally ​

Maker-Checker must first be enabled at the system level via the Global Configuration API:

bash
PUT /fineract-provider/api/v1/configurations/{configId}
Authorization: Basic <base64>
Fineract-Platform-TenantId: default
Content-Type: application/json
json
{
  "enabled": true
}

Find the Maker-Checker configuration ID first:

bash
GET /fineract-provider/api/v1/configurations

Look for the entry with "name": "maker-checker" and use its id.

Disabled by default

Maker-Checker is disabled on a fresh Fineract installation. Enabling it immediately affects all task types that have been configured to require approval.


Configuring Which Tasks Require Approval ​

Not all operations need to go through maker-checker. Maker-checker is enabled per permission (task), through the Permissions API. To see which permissions are currently enabled for maker-checker, list them with the makerCheckerable flag:

bash
GET /fineract-provider/api/v1/permissions?makerCheckerable=true

With makerCheckerable=true, each permission's selected attribute shows whether it is enabled for maker-checker. To change that, send a map of permission code to true (enable) or false (disable):

bash
PUT /fineract-provider/api/v1/permissions
json
{
  "permissions": {
    "APPROVE_LOAN": true,
    "DISBURSE_LOAN": true
  }
}

The change only has an effect while the global maker-checker configuration is enabled.

Commonly configured task types ​

TaskRecommended for maker-checker
Loan application submissionOptional
Loan approvalYes - regulated environments
Loan disbursementYes - high value
Loan write-offYes - irreversible
Savings withdrawalYes - above threshold
Journal entry postingYes - accounting integrity
User creationOptional
Role/permission changesYes
Client creationOptional
Charge waiverYes

Roles and Permissions ​

Two distinct permissions control maker-checker participation:

PermissionCodeWho needs it
Submit (maker)Standard operation permission (e.g. APPROVE_LOAN)Anyone submitting actions for approval
Approve/reject (checker)CHECKER_SUPER_USER or task-specific checker permissionCheckers only

A user with APPROVE_LOAN but without checker permission can submit loan approvals for review. A user with checker permission can approve or reject them.

Typically:

  • Loan officers have maker permissions - they submit actions
  • Branch managers or supervisors have checker permissions - they approve
  • Super users have both (CHECKER_SUPER_USER and ALL_FUNCTIONS skip the same-user check, so avoid letting them approve their own actions)

The Checker Inbox ​

Pending maker-checker items accumulate in the Checker Inbox. Query it via:

bash
GET /fineract-provider/api/v1/makercheckers

This returns the entries awaiting approval that the requesting user is allowed to check. You can narrow it with query parameters such as actionName, entityName, makerId, officeId and clientId. The loan filter is spelled loanid in all lowercase (?loanid=123); ?loanId=123 is silently ignored. A companion GET /fineract-provider/api/v1/makercheckers/searchtemplate returns the action names, entity names and users to build a filter UI from.

Each item includes:

json
{
  "id": 1234,
  "actionName": "APPROVE",
  "entityName": "LOAN",
  "resourceId": 567,
  "maker": "loan.officer",
  "madeOnDate": "2025-03-01T10:30:00Z",
  "processingResult": "Awaiting Approval"
}

Approving a Pending Action ​

bash
POST /fineract-provider/api/v1/makercheckers/{auditId}?command=approve

No request body required. The original action executes immediately upon approval.


Rejecting a Pending Action ​

bash
POST /fineract-provider/api/v1/makercheckers/{auditId}?command=reject

The pending action is not executed. The entry stays in the audit log with the Rejected result, the checker and the time of the decision, so anyone with audit read permission can find it there by reading /audits and looking for the Rejected processingResult. Fineract does not send the maker a notification, so tell them through your own process.


Bulk Approval ​

For high-volume environments, checkers can approve multiple items at once. Retrieve all pending items, filter for the desired subset, and approve each:

bash
# Get all pending loan approvals
GET /fineract-provider/api/v1/makercheckers?entityName=LOAN&actionName=APPROVE

# Approve each
POST /fineract-provider/api/v1/makercheckers/{auditId1}?command=approve
POST /fineract-provider/api/v1/makercheckers/{auditId2}?command=approve

Viewing the Audit Trail ​

All maker-checker submissions - processed, rejected, and pending - are recorded in the audit log:

bash
# All audit entries for a specific loan
GET /fineract-provider/api/v1/audits?entityName=LOAN&resourceId={loanId}

# All actions by a specific user
GET /fineract-provider/api/v1/audits?makerId={userId}

# All actions approved by a checker
GET /fineract-provider/api/v1/audits?checkerId={userId}

Each audit entry carries a processingResult (for example Awaiting Approval or Rejected). To find entries by result, read them from /audits and filter on processingResult in your client. Fineract also has a status query parameter on /audits, but we have only confirmed it in the source and have not run it, so it may not work on every database; do not rely on it.

Reading the audit log needs the READ_AUDIT permission (or ALL_FUNCTIONS / ALL_FUNCTIONS_READ). Once an entry has been processed or rejected it cannot be changed or deleted, which is important for regulatory compliance. Only entries still awaiting approval can be deleted, with DELETE /fineract-provider/api/v1/makercheckers/{auditId}, subject to the same checker rules as approval.


Processing Result Codes ​

CodeMeaning
0Invalid
1Processed (executed, including entries approved by a checker)
2Awaiting approval
3Rejected
4Under processing
5Error

Maker-Checker and API Integrations ​

When maker-checker is enabled for a task type, API calls that trigger that task will return a 200 OK with a response indicating the action is pending rather than executed:

json
{
  "commandId": 1234,
  "resourceId": 123,
  "rollbackTransaction": true
}

The commandId here is the audit entry ID (the id used by /audits/{auditId} and /makercheckers/{auditId}), and rollbackTransaction: true signals that the change was not applied. The resourceId is the ID of the affected entity (the loan in this example); it is absent when there is no entity yet, for example for create-type commands. Your integration must detect this and either:

  • Poll GET /fineract-provider/api/v1/audits/{commandId} until processingResult is no longer Awaiting Approval
  • Or notify a human checker to review the inbox

Integration impact

Enabling maker-checker on operations that your integration calls synchronously will break the synchronous flow - the operation will not have executed when the API returns. Always test integrations against a maker-checker enabled environment before production.


Disabling Maker-Checker for Specific Environments ​

For development or testing environments where the approval loop is not needed:

bash
PUT /fineract-provider/api/v1/configurations/{makercheckerConfigId}
json
{
  "enabled": false
}

Or disable it per task type to leave global maker-checker on but skip it for low-risk operations.