Appearance
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
- A maker initiates an action - for example, approving a loan
- Instead of executing immediately, the action is placed in a pending queue
- A checker (a different user with the appropriate permission) reviews the pending action
- The checker approves or rejects it
- 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/jsonjson
{
"enabled": true
}Find the Maker-Checker configuration ID first:
bash
GET /fineract-provider/api/v1/configurationsLook 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=trueWith 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/permissionsjson
{
"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
| Task | Recommended for maker-checker |
|---|---|
| Loan application submission | Optional |
| Loan approval | Yes - regulated environments |
| Loan disbursement | Yes - high value |
| Loan write-off | Yes - irreversible |
| Savings withdrawal | Yes - above threshold |
| Journal entry posting | Yes - accounting integrity |
| User creation | Optional |
| Role/permission changes | Yes |
| Client creation | Optional |
| Charge waiver | Yes |
Roles and Permissions
Two distinct permissions control maker-checker participation:
| Permission | Code | Who 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 permission | Checkers 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_USERandALL_FUNCTIONSskip 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/makercheckersThis 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=approveNo request body required. The original action executes immediately upon approval.
Rejecting a Pending Action
bash
POST /fineract-provider/api/v1/makercheckers/{auditId}?command=rejectThe 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=approveViewing 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
| Code | Meaning |
|---|---|
0 | Invalid |
1 | Processed (executed, including entries approved by a checker) |
2 | Awaiting approval |
3 | Rejected |
4 | Under processing |
5 | Error |
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}untilprocessingResultis no longerAwaiting 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.