← All notes

Fintech

KYC is a state machine, not a boolean

A verified flag cannot express "documents submitted, one rejected, awaiting re-upload, provisionally allowed to transact up to a limit".

  • Invexa Technologies
  • 2 min read

Most products start with a kyc_verified boolean. It is true or false, it gates the features that need it, and it is wrong within a month of talking to a compliance team.

What the boolean cannot say

Real verification has intermediate states that carry real permissions. A user who has submitted documents but is awaiting review may be allowed to hold a balance and not withdraw. A user whose address proof was rejected needs to be told which document failed and why. A user verified two years ago may need re-verification under a rule that did not exist when they signed up.

A boolean flattens all of that into a permission decision made somewhere else, usually in a controller, usually differently in each place.

Model the states explicitly

kyc_status
  unstarted
  documents_pending       -- waiting on the user
  under_review            -- waiting on us or a provider
  additional_info_needed
  verified
  rejected
  expired

Each transition is a row, not an update:

kyc_transitions
  user_id
  from_status
  to_status
  reason_code
  actor            -- system | provider | reviewer id
  evidence_ref     -- what was looked at
  occurred_at

Append-only, for the same reason the ledger is. When a regulator asks why this account was allowed to transact on a given date, “the flag was true” is not an answer. The transition log is.

Permissions derive from state

One function maps status to capabilities, and every caller goes through it. Withdraw requires verified. Holding a balance is allowed under review. Deposits are allowed for anything but rejected.

Scatter these checks across controllers and the day the rules change you will find four places that agree and one that does not.

Expiry is a scheduled transition, not a cron hack

A verified record carries a valid_until. A job moves rows to expired when it passes, writing a transition like everything else. The user’s capabilities change because their state changed — not because a different query started returning different results.

The cost

An enum, a transitions table, and one permissions function. Perhaps two days. It is the difference between a compliance review that reads your log and a compliance review that reads your code.

Next step

Have a project in mind?

A 30-minute call is usually enough to know whether we are the right team for it. If we are not, we will say so.

Start a project

Replies within one working day