FH
FinHero Adapter: Telco
TELCO-CARRIER INTEGRATION PLAYBOOK

Designing APIs for Alternative Credit Scoring

As a major telecom provider, your account history acts as a rich alternative credit signal. This blueprint helps your engineering and product teams design matching lookup endpoints and build FinHero-compatible source adapters to safely ingest alternative financial data.

Product Manager Focus

Design a secure, low-latency subscriber payment query API. Leverage existing national-ID attributes for KYC.

  • Identifier: Resolve accounts via Malaysian IC number.
  • Metrics: Package payment behavior, ARPU, and contract tenure.
  • Outcome: Unlock alternative credit options for thin-file subscriber cohorts.

API Engineering Focus

Implement the lightweight mapping layer (source adapter) matching FinHero's JS/TS engine interfaces.

  • Fetch Method: Secure HTTPS lookup fetching raw subscribers data.
  • Extract Method: Local deterministic conversion, units normalization.
  • Testing: Local schema validations and offline mock runner checks.

Integration & QA Focus

Ensure correctness and consistency of exported signals in local mock sandbox environments before deployment.

  • Linter: Structural JSON-schema checks on configuration manifest.
  • Deterministic: Same mock data input guarantees matching score metrics.
  • Sandbox: Run isolated mock API and adapter container configs.
๐Ÿ’ก

Understanding the "Source Adapter" Architecture

You don't need to install or run the full FinHero server stack to build the integration. The toolkit provides a fully offline validation suite. You implement a manifest file and mapping scripts which FinHero dynamically imports.

๐Ÿ“Š

Data Extraction Pipeline

How subscriber identity converts to credit underwriting metrics. The mapping runs entirely inside the sandboxed FinSys execution environment using your custom mapping rules.

1. FinHero Host Receives Application Identity: { IC, Name } 2. Adapter: fetch() Query Telco Endpoint HTTP POST /lookup 3. Adapter: extract() Normalize Raw Data Map to schema types 4. Scoring Ingestion Canonical SQL Table ihs_alt_data_telco
STEP 01
Trigger Application
FinHero identifies a finalized application and extracts applicant IC (Malaysian National ID) and name parameters.
STEP 02
Upstream Query
The adapter executes `fetch()`, forwarding credentials and query headers securely to the Telco API to obtain payment details.
STEP 03
Local Extract
The adapter's pure function `extract()` transforms the API payload, calculating tenure ratios and formatting structures.
STEP 04
Commit to Schema
Normalized fields are stored inside FinHero database schemas for real-time risk profile scoring evaluation.
๐Ÿ“‹

Canonical Telco Fields

Your API does not need to expose a matching database scheme directly. However, your adapter must map what your API produces into a subset of these 7 fields expected by FinHero's telco-carrier registry.

Field Key Data Type Constraint / Range Credit Scoring Interpretation
onTimePaymentRatio24m number ratio [0.0 - 1.0] Fraction of bills paid on time over the last 24 months. Primary risk predictor. Values ≥0.95 signal strong history.
tenureMonths number months [0 - 600] Account age in months. Values ≥48 months trigger scoring boosts to offset thin formal credit files.
suspensionsCount24m number count [0 - 100] Number of suspensions triggered by non-payment in the last 24 months. Values ≥3 flag potential financial distress.
lateDays24m number days [0 - 800] Cumulative count of days overdue across all bill cycles in the trailing 24-month window.
arpu number MYR [0 - 10000] Average monthly bill revenue (RM). Acts as a proxy for consumer disposable spending capacity.
handsetFinancingActive boolean true / false Indicates whether the user has an active handset installment payment plan (device EMI active).
handsetFinancingDelinquent boolean true / false Indicates if there is an active device payment default. Flags high risk even if standard service bills are paid.
๐Ÿงช

API Mapping Playground

Select a canned user persona from our fake database mockup, or customize the values in the control panel to see live calculations demonstrating how the raw API parameters translate into canonical scoring fields.

Inputs Controls interactive
UPSTREAM_API_PAYLOAD.JSON Telco Upstream
โž”
extract()
CANONICAL_EXTRACTION.JSON Adapter Output
๐Ÿ’ป

Source Adapter Implementation

Complete reference setup code for your custom adapter module. The files must reside in the same folder and follow directory export bindings. Click on the tabs below to copy the boilerplate templates.

// manifest.json: Declares entrypoint & outputs metadata
{
  "manifestVersion": 1,
  "id": "telco-carrier-adapter-v1",
  "displayName": "Carrier Alternative Data Integration",
  "category": "telco-carrier",
  "version": 1,
  "produces": [
    "tenureMonths",
    "onTimePaymentRatio24m",
    "lateDays24m",
    "suspensionsCount24m",
    "arpu",
    "handsetFinancingActive",
    "handsetFinancingDelinquent"
  ],
  "requiredIdentityFields": [],
  "implementation": {
    "type": "typescript",
    "entryPoint": "extract.mjs"
  }
}
// extract.mjs: Queries API, transforms metrics to canonical fields
const API_URL = process.env.TELCO_UPSTREAM_URL ?? "http://api-internal.telco:4100";
const API_KEY = process.env.TELCO_API_TOKEN ?? "prod-key-token";

const adapter = {
  id: "telco-carrier-adapter-v1",
  category: "telco-carrier",
  version: 1,
  produces: [
    "tenureMonths",
    "onTimePaymentRatio24m",
    "lateDays24m",
    "suspensionsCount24m",
    "arpu",
    "handsetFinancingActive",
    "handsetFinancingDelinquent"
  ],

  async fetch(identity) {
    if (!identity?.ic || !identity?.fullName) {
      throw new Error("Missing candidate identification details: ic/fullName");
    }
    const res = await fetch(`${API_URL}/v1/subscribers/lookup`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        "x-api-key": API_KEY
      },
      body: JSON.stringify({ ic: identity.ic, fullName: identity.fullName })
    });
    
    if (!res.ok) {
      throw new Error(`Telco lookup connection error: HTTP ${res.status}`);
    }
    return res.json();
  },

  async extract(raw) {
    const since = new Date(raw.subscriberSince);
    const now = new Date();
    const tenureMonths = Math.max(0, (now.getFullYear() - since.getFullYear()) * 12 + (now.getMonth() - since.getMonth()));
    
    const history = raw.paymentHistory ?? {};
    const onTime = Number(history.billsPaidOnTime ?? 0);
    const late = Number(history.billsPaidLate ?? 0);
    const unpaid = Number(history.billsUnpaid ?? 0);
    const total = onTime + late + unpaid;
    const onTimeRatio = total > 0 ? Number((onTime / total).toFixed(4)) : 0;
    
    // Assume an average of 15 late days per late bill instance
    const lateDays = late * 15;
    const suspensions = Number(history.suspensions ?? (unpaid > 0 ? 1 : 0));
    const handsetActive = Boolean(raw.handsetFinancing?.active ?? false);
    const handsetDelinquent = Boolean(raw.handsetFinancing?.delinquent ?? false);

    return [{
      instanceKey: "default",
      observedAt: new Date().toISOString(),
      values: {
        tenureMonths: tenureMonths,
        onTimePaymentRatio24m: onTimeRatio,
        lateDays24m: lateDays,
        suspensionsCount24m: suspensions,
        arpu: Number(raw.averageMonthlyArpuMyr ?? 0),
        handsetFinancingActive: handsetActive,
        handsetFinancingDelinquent: handsetDelinquent
      }
    }];
  }
};

export default adapter;
// test.mjs: Standard offline verification test block
import { runFixtures } from "@finsys/adapter-toolkit";
import adapter from "./extract.mjs";

const fixtures = [
  {
    name: "strong-subscriber",
    identity: { ihsId: 1, ic: "850101015432", fullName: "Aiman bin Hassan" },
    expected: [
      {
        instanceKey: "default",
        values: {
          onTimePaymentRatio24m: 1.0,
          suspensionsCount24m: 0
        }
      }
    ]
  }
];

const results = await runFixtures(adapter, fixtures);
console.log(results);
if (results.some(r => !r.ok)) {
  console.error("โŒ Offline verification suite failed");
  process.exit(1);
} else {
  console.log("โœ… All integration fixtures passed successfully");
}
๐Ÿ› ๏ธ

Testing & Validation Run Commands

Validate your manifest mapping format and run execution simulation tests locally. Use these command recipes in your terminal console or CI/CD pipelines to ensure compliance before submission.

bash โ€” manifest validation
$ npx finsys-adapter-toolkit validate ./my-telco-adapter
โœ“ Parsing manifest.json schema... โœ“ Resolved entry point extract.mjs โœ“ Validated categories mapping matches 'telco-carrier' fields... โœ… Validation passed successfully! Ready for ingestion registry.
bash โ€” run offline fixtures
$ node ./my-telco-adapter/test.mjs
Running 1 fixture checks against adapter... [Fixture: strong-subscriber] - Extract values check: OK - OnTime Ratio check: 1.0 (Expected: 1.0) - OK - Suspensions check: 0 (Expected: 0) - OK โœ… All 1 tests passed.
docker โ€” local sandbox deployment
$ docker compose -f examples/fake-telco/docker-compose.yml up --build -d
Building fake-telco-api... Creating fake-telco-api-container ... done Attaching API endpoint to localhost:4100 Sending mock subscriber ping check: HTTP 200 OK