SStratum APIs
← Blog

Perpetual KYB under MLR 2017: what your monitoring needs to do

25 April 2026

Most KYB processes have a sharp moment of effort at onboarding (collect documents, verify directors, check the company exists, check the regulator) and then very little after that. A name lands on a watchlist eighteen months later and nobody notices. A firm loses its FCA permissions and continues to be paid as a counterparty for another quarter. A director is added who would have failed the original screen.

The Money Laundering, Terrorist Financing and Transfer of Funds (Information on the Payer) Regulations 2017, usually shortened to MLR 2017, treats this as a problem. Regulation 28(11) requires firms to conduct "ongoing monitoring of a business relationship", including scrutinising transactions and keeping documents and information up to date. The word doing the work is ongoing. The FCA has clarified through a series of enforcement actions that this means active monitoring on a continuous basis, not annual file reviews.

What "ongoing" actually requires

The regulation is light on operational detail because it has to fit firms ranging from small accountancy practices to global banks. The FCA's Financial Crime Guide (FCG) and the JMLSG guidance fill in the practical expectations. For a B2B fintech with regulated counterparties, the substantive requirements are:

  • Detect changes in the customer's regulatory status promptly. "Promptly" is not defined, but enforcement decisions have treated "within a few business days" as the bar for material changes.
  • Detect changes in beneficial ownership, especially additions of persons not screened against sanctions and PEP lists.
  • Re-screen against sanctions lists as those lists update, not just at onboarding.
  • Re-evaluate risk rating when material changes occur (status downgrade, ownership change, new adverse media).
  • Keep the underlying KYC documents current, with re-collection cadence proportionate to risk rating.

Of these, the first three are the most automatable. The last two depend on policy decisions you cannot outsource to a script.

The minimum viable monitoring loop

A workable pattern, in pseudocode and small enough to fit in a single Lambda or cron job:

// Once per day, for every customer with an active business relationship
for (const customer of activeCustomers) {
  const before = await loadLastSnapshot(customer.id);
  const now = await Promise.all([
    fcaStatus(customer.frn),
    companiesHouseSnapshot(customer.chNumber),
    sanctionsScreen(customer.legalName, customer.directors),
  ]);

  const diff = compare(before, now);
  if (diff.materialChange) {
    await alert(customer, diff);
    await reRiskRate(customer);
  }
  await persistSnapshot(customer.id, now);
}

The trick is in the compare step. A "material change" is not every diff. New filing of unaudited accounts is not material. A change in registered office is not, on its own, material. A change from Authorised to Cancelled is. So is the addition of a director who appears on a sanctions list. So is a new PSC notification. The materiality rules are policy decisions; the detection of the diff is plumbing.

Why most teams stop at onboarding

Three reasons recur:

  1. Building the snapshot store is more work than it looks. You need to persist enough of yesterday's state to detect changes today. For 5,000 customers and a few sources each, this is a few million rows after a year, with a TTL strategy and a normalised schema. Easy in DynamoDB or Postgres, but easy to defer.
  2. The official APIs do not push. The FCA does not webhook you when a firm's status changes. Companies House offers a streaming API but it ships every change for every UK company, not just yours; you filter. So "ongoing monitoring" means "daily polling", and daily polling means rate limits, error handling, and cron infrastructure to maintain.
  3. Materiality scoring is policy work, not code work. Engineers can ship a diff. Defining which diffs are material requires a compliance officer and a written rule. Many teams ship the diff, then never define the rules, then disable the alerting because it is too noisy.

The audit-trail requirement nobody talks about

MLR 2017 Regulation 40 requires firms to keep records demonstrating compliance, including monitoring activity, for at least five years after the business relationship ends. In a supervisory visit, the FCA will ask: when did you last verify this counterparty's regulatory status? They will not accept "we use API X"; they will want a date-stamped record per check.

This means every monitoring run needs a persisted audit row, even when nothing changed. A common implementation:

// Audit row schema
{
  customerId: string,
  checkedAt: string,        // ISO 8601
  source: 'fca' | 'companies-house' | 'sanctions',
  resourceId: string,       // FRN, CH number, etc.
  resultStatus: string,     // normalised status
  diffFromPrevious: 'no-change' | 'minor' | 'material',
  ttlExpiresAt: number,     // 5+ years from now
}

The records are useful even outside enforcement context. They are the evidence base for a counterparty disputing why you ended the relationship, and they make it possible to answer "when did we first know about this" in any incident review.

Build versus buy

The build version of perpetual KYB takes a few weeks of engineering and a rolling commitment of compliance attention. For most B2B fintechs running 50 to 500 counterparties, that effort is worth it once. For everyone else (single-product startups, accountancy practices, brokers), the alternative is to subscribe to a service that exposes the change feed and audit log as a single API, and put the saved time into materiality policy that actually fires the right alerts.

We built our FCA Verification API with this in mind: it ships a daily change feed for every monitored FRN, plus a 365-day audit log of every authenticated check, so the "snapshot store + cron + record-keeping" layer is one HTTP integration rather than a quarter of engineering work. Whether you build or buy, the trap to avoid is the one in the middle: a halfway-built monitoring loop that detects some changes, sometimes, without the audit trail to prove you were watching.

Perpetual KYB under MLR 2017: what your monitoring needs to do | Stratum APIs