Skip to Content
ConceptsFingerprint enrollment

Fingerprint enrollment lifecycle

On the USB reader path, enrolling a finger takes two captures, not one. The first capture records the finger as an unverified print; a second, matching capture promotes it to verified. A subject is only enrolled once enough of their fingers are verified.

Understand these states before wiring an enrollment flow — they explain why a freshly captured finger doesn’t yet count, and why a finger can silently disappear and need re-capturing. For the code, see Use a USB fingerprint reader.

A finger’s states

(none) --enroll()--> unverified --verify() matches--> verified ^ | | | verify() keeps failing → attempts exhausted +----------------------+ (backend resets the finger)
  • unverifiedreader.enroll({ enrollee, position }) captured the finger and stored a template, but nothing has confirmed it yet. The position appears in fetchEnrolled().nonVerified. An unverified finger does not count toward the subject being enrolled.
  • verifiedreader.verify({ enrollee, position }) captured the same finger a second time and it matched the stored template. The backend promotes the record: the position moves from nonVerified to verified, and that finger’s enrollment is complete.

verify() confirms a fresh capture against the enrollee’s stored record. During enrollment that confirmation is what promotes an unverified print to verified; the same call is also how you check a claimed identity once a subject is enrolled.

Subject status: partial vs full

fetchEnrolled reports an enrollmentStatus for the whole subject:

  • 'partially_enrolled' — some target fingers are still missing or unverified. The subject cannot yet be treated as enrolled.
  • 'fully_enrolled' — enough fingers are verified. How many is required is configured by the backend, not fixed in the SDK, so let enrollmentStatus tell you when you’re done rather than counting fingers yourself.

Only verified fingers advance the status. Capturing ten fingers with enroll() alone leaves the subject partially_enrolled — each one still needs its second (verify) capture.

fetchEnrolled is the source of truth

reader.fetchEnrolled(enrollee) returns { verified, nonVerified, total, enrollmentStatus, notes?, raw }. It is a metadata lookup — no hardware, no capture — so you can call it freely to check progress:

  • verified — positions that are complete. Skip these.
  • nonVerified — positions captured but still awaiting their verify capture.
  • enrollmentStatus — whether the subject is done.

Track enrollment progress by re-reading fetchEnrolled after each capture and branching on it — never with your own counters.

Reset and re-enroll

Confirming a finger can fail — a smudged sensor, the wrong finger, a poor placement. verify() resolving { matched: false } is a normal, expected outcome, not an error: the finger stays unverified and you prompt the user to try the verify capture again.

The backend allows only a limited number of failed verify attempts per finger (also backend-configured). Once they’re exhausted it resets the finger, deleting the unverified record. You observe this as the position disappearing from both verified and nonVerified in the next fetchEnrolled. There is no reliable typed “attempts remaining” counter in the SDK — detect the reset by re-reading fetchEnrolled and noticing the finger is gone.

To recover, start that finger over: enroll() it again (a fresh unverified record), then verify() to confirm.

Putting it together

For each target finger:

  1. enroll() — first capture, stores an unverified print.
  2. verify() — second capture, promotes to verified on a match; retry while matched is false.
  3. fetchEnrolled() — if the finger vanished from both lists, the backend reset it; go back to step 1.
  4. Repeat until enrollmentStatus === 'fully_enrolled'.

See Use a USB fingerprint reader for the full worked loop.

Used in