Skip to content

Delinquency Bucketing

Delinquency bucketing is Fineract's mechanism for classifying overdue loans into named risk tiers based on how many days past due they are. Buckets drive portfolio risk reporting (PAR metrics), automated charge triggers, and business event signals for collections workflows.


What Is a Delinquency Bucket?

A delinquency bucket is a named range of days-past-due (DPD). Common examples:

Bucket nameDPD rangeTypical PAR metric
Current0 daysNot delinquent
1-30 days1 - 30PAR 1
31-60 days31 - 60PAR 30
61-90 days61 - 90PAR 60
91-180 days91 - 180PAR 90
180+ days181+PAR 180 / Write-off risk

The ranges, names, and number of buckets are fully configurable. An institution might define six buckets or twenty - depending on their portfolio management and regulatory reporting requirements.


How Bucket Classification Works

Classification runs automatically during COB (Close of Business). Each night, Fineract:

  1. Calculates the number of days past due for every active overdue loan
  2. Compares DPD against all configured bucket ranges
  3. Assigns the loan to the highest matching bucket
  4. Records the classification in the loan's delinquency_range field
  5. Emits a LoanDelinquencyRangeChangeBusinessEvent business event if the bucket changed since the last run

Bucket assignment is updated every night. A loan that moves from the 1-30 bucket to the 31-60 bucket (because it remained unpaid for another month) is reclassified automatically on the COB run that crosses the threshold.


Creating a Delinquency Bucket Configuration

Step 1: Create Individual Ranges

bash
POST /fineract-provider/api/v1/delinquency/ranges
Authorization: Basic <base64>
Fineract-Platform-TenantId: default
Content-Type: application/json
json
{
  "classification": "1 to 30 days",
  "minimumAgeDays": 1,
  "maximumAgeDays": 30,
  "locale": "en"
}

Create one range per bucket tier. For the highest tier (open-ended), omit maximumAgeDays or set it to a very large number:

json
{
  "classification": "181+ days",
  "minimumAgeDays": 181,
  "locale": "en"
}

Step 2: Create a Delinquency Bucket

A bucket is a named collection of ranges:

bash
POST /fineract-provider/api/v1/delinquency/buckets
json
{
  "name": "Standard Delinquency Bucket",
  "ranges": [1, 2, 3, 4, 5]
}

The ranges array contains the IDs of the ranges created in Step 1.

Step 3: Assign the Bucket to a Loan Product

bash
PUT /fineract-provider/api/v1/loanproducts/{productId}
json
{
  "delinquencyBucketId": 1
}

All loans created from this product will be classified using the assigned bucket.


Reading Delinquency Classification

On a single loan

bash
GET /fineract-provider/api/v1/loans/{loanId}/delinquency

Response includes:

json
{
  "delinquentDate": "2025-01-15",
  "delinquentDays": 52,
  "delinquentAmount": 1250.00,
  "installmentLevelDelinquency": [
    {
      "rangeId": 2,
      "classification": "31 to 60 days",
      "delinquentAmount": 1250.00
    }
  ]
}

Installment-level vs Loan-level delinquency

Fineract supports delinquency tracking at two levels:

  • Loan-level - the bucket is determined by the oldest unpaid installment's DPD
  • Installment-level - each overdue installment is independently classified into a bucket, allowing a loan to span multiple buckets if it has installments overdue by different amounts of time

Installment-level delinquency gives more granular data for collections prioritisation. Enable it on the loan product with:

json
{
  "enableInstallmentLevelDelinquency": true
}

Listing All Delinquent Loans

Use the search API to filter loans by delinquency range:

bash
GET /fineract-provider/api/v1/loans/search?delinquencyRangeId=2

For portfolio-level reporting, use the Fineract reporting module or query the database directly:

sql
-- Loans by delinquency bucket
SELECT
  dr.classification AS bucket,
  COUNT(l.id) AS loan_count,
  SUM(ls.principal_outstanding_derived) AS outstanding_principal
FROM m_loan l
JOIN m_delinquency_range dr ON l.loan_delinquency_range_id = dr.id
JOIN m_loan_repayment_schedule ls ON ls.loan_id = l.id
WHERE l.loan_status_id = 300  -- Active loans
GROUP BY dr.classification
ORDER BY dr.minimum_age_days;

PAR (Portfolio at Risk) Calculation

PAR X is the percentage of the outstanding portfolio principal where at least one installment is X or more days past due.

sql
-- PAR 30: loans with 30+ DPD as % of total active portfolio
SELECT
  (
    SELECT SUM(principal_outstanding_derived)
    FROM m_loan
    WHERE delinquent_days >= 30
    AND loan_status_id = 300
  ) /
  (
    SELECT SUM(principal_outstanding_derived)
    FROM m_loan
    WHERE loan_status_id = 300
  ) * 100 AS par30_percentage;

PAR values are always calculated against Business Date (not system date) to stay consistent with Fineract's own view of overdue status.


Business Events for Collections Workflows

Every time a loan moves to a new delinquency bucket, Fineract emits:

LoanDelinquencyRangeChangeBusinessEvent

This event contains the loan ID, the previous bucket, the new bucket, and the current DPD. Subscribe to this event via Kafka or JMS to:

  • Trigger escalation in your collections system
  • Send automated SMS or push notifications to borrowers
  • Assign the loan to a specialist collections officer
  • Update risk scoring in your CRM

See the Business Events guide for connector setup.

Example event payload

json
{
  "businessEventType": "LoanDelinquencyRangeChangeBusinessEvent",
  "loanId": 1234,
  "loanExternalId": "LOAN-ABC-001",
  "delinquencyRange": {
    "id": 3,
    "classification": "61 to 90 days"
  },
  "delinquentDays": 67,
  "delinquentDate": "2025-01-15"
}

Viewing Configured Buckets and Ranges

bash
# All configured ranges
GET /fineract-provider/api/v1/delinquency/ranges

# All configured buckets
GET /fineract-provider/api/v1/delinquency/buckets

# A specific bucket with its ranges
GET /fineract-provider/api/v1/delinquency/buckets/{bucketId}

Best Practices

  • Align bucket boundaries with regulatory requirements - many central banks require specific PAR 30/60/90 reporting, so match your bucket ranges to those thresholds
  • Use installment-level delinquency for complex products - especially for progressive or multi-tranche loans where individual installments may have very different overdue ages
  • Subscribe to LoanDelinquencyRangeChangeBusinessEvent for real-time collections triggers rather than polling the API nightly
  • Re-evaluate bucket configuration after portfolio growth - a single bucket covering 1-90 days is fine for small portfolios but obscures risk concentration as volumes grow