Skip to main content

Verification Statuses

This guide explains the status lifecycle for applicants, documents, and screening checks in TrustGate. Understanding these statuses helps you build effective verification flows and handle edge cases.

Applicant Statuses

Status Diagram

     ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
‚Üì ‚Üì
↓ ⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎ ⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎ ⅎⅎⅎⅎⅎⅎⅎⅎⅎ ↓
↓ ↓ PENDING ↓ↀↀ>↓ IN_PROGRESS ↓ↀↀ>↓ REVIEW ↓ ↓
↓ ⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎ ⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎ ⅎⅎⅎⅎⅎⅎⅎⅎⅎ ↓
‚Üì ‚Üì ‚Üì
↓ ↔ↀↀↀↀↀↀↀↀↀↀↀ↏ↀↀↀↀↀↀↀↀ← ↓
‚Üì v v ‚Üì
↓ ⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎ ⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎ ↓
‚Üì ‚Üì APPROVED ‚Üì ‚Üì REJECTED ‚Üì ‚Üì
↓ ⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎ ⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎ ↓
‚Üì ‚Üì
↓ ⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎ ↓
‚Üì (any status)‚ÜÄ>‚Üì WITHDRAWN ‚Üì ‚Üì
↓ ⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎ ↓
‚Üì ‚Üì
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

Status Definitions

StatusDescriptionTypical DurationNext Actions
pendingApplicant created, awaiting verification stepsMinutes to hoursUpload documents, complete steps
in_progressVerification steps being completedHours to daysComplete remaining steps
reviewAll steps complete, awaiting human reviewHours to daysReviewer decision
approvedVerification successful, applicant approvedTerminalGrant access, onboard
rejectedVerification failed, applicant rejectedTerminalAppeal process (optional)
withdrawnApplicant withdrew applicationTerminalRe-apply (if allowed)

Status Transitions

Pending → In Progress

Triggered when:

  • First document uploaded
  • First verification step started
  • Screening initiated
# Example: First document upload triggers status change
document = upload_document(applicant_id, "passport", file)
# Applicant status automatically updates to "in_progress"

In Progress → Review

Triggered when:

  • All required workflow steps completed
  • Automatic review conditions not met (risk score, hits)
# Example: Check if ready for review
applicant = get_applicant(applicant_id)
steps_complete = all(s["status"] == "complete" for s in applicant["steps"])

if steps_complete:
# Status automatically moves to "review" if screening has hits
# or risk score exceeds auto-approve threshold

Review → Approved/Rejected

Triggered when:

  • Reviewer makes decision via dashboard or API
# Approve applicant
response = requests.post(
f"{BASE_URL}/applicants/{applicant_id}/review",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"decision": "approved"},
)

# Reject applicant
response = requests.post(
f"{BASE_URL}/applicants/{applicant_id}/review",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"decision": "rejected"},
)

Auto-Approval (Pending/In Progress → Approved)

If configured, low-risk applicants can be auto-approved:

Conditions for auto-approval:
‚úì All required steps completed
‚úì No screening hits
‚úì Risk score ‚â§ auto_approve_threshold (default: 30)
‚úì No risk flags

Risk Levels and Routing

Risk ScoreRisk LevelDefault Routing
0-30LowAuto-approve (if enabled)
31-60MediumQueue for review
61-80HighPriority review
81-100CriticalEscalate to senior reviewer

Document Statuses

Status Diagram

ⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎ    ⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎ    ⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎ
↓ PENDING ↓ↀↀ>↓ PROCESSING ↓ↀ↏ↀ>↓ VERIFIED ↓
ⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎ ⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎ ⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎ
‚Üì
v
ⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎ
‚Üì REJECTED ‚Üì
ⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎ
‚Üì
v
ⅎⅎⅎⅎⅎⅎⅎⅎⅎ
‚Üì EXPIRED ‚Üì (Post-verification, if expiry date passed)
ⅎⅎⅎⅎⅎⅎⅎⅎⅎ

Status Definitions

StatusDescriptionDuration
pendingDocument uploaded, awaiting processingSeconds
processingOCR and verification in progress2-10 seconds
verifiedSuccessfully verified, data extractedTerminal (may expire)
rejectedVerification failedTerminal
expiredDocument expiry date has passedTerminal

Rejection Reasons

ReasonDescriptionUser Action
image_quality_lowImage too blurry/low resolutionRetake photo
document_expiredDocument past expiry dateUpload valid document
tampering_suspectedSigns of digital manipulationUpload unaltered document
partial_documentDocument edges cut offCapture entire document
glare_detectedLight reflection on documentRetake without flash
wrong_document_typeNot the expected documentUpload correct type
ocr_failedCouldn't extract required dataUpload clearer image
face_not_detectedNo face found (ID documents)Ensure photo is visible

Screening Check Statuses

Status Diagram

ⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎ    ⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎ    ⅎⅎⅎⅎⅎⅎⅎⅎⅎ
↓ PENDING ↓ↀↀ>↓ PROCESSING ↓ↀ↏ↀ>↓ CLEAR ↓
ⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎ ⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎⅎ ⅎⅎⅎⅎⅎⅎⅎⅎⅎ
↔ↀↀ↏ↀↀↀↀ←
v v
ⅎⅎⅎⅎⅎⅎⅎ ⅎⅎⅎⅎⅎⅎⅎⅎⅎ
‚Üì HIT ‚Üì ‚Üì ERROR ‚Üì
ⅎⅎⅎⅎⅎⅎⅎ ⅎⅎⅎⅎⅎⅎⅎⅎⅎ

Status Definitions

StatusDescriptionNext Actions
pendingCheck queued for processingWait
processingCheck running against listsWait
clearNo matches foundContinue verification
hitPotential matches foundReview hits
errorCheck failed (retry possible)Retry or escalate

Screening Hit Statuses

Resolution Status

StatusDescriptionWho Sets
pendingHit awaiting reviewSystem (default)
confirmed_trueHit confirmed as real matchReviewer
confirmed_falseHit confirmed as false positiveReviewer

Step Statuses

Verification steps (document, selfie, screening) have their own statuses:

StatusDescription
pendingStep not started
in_progressStep started but not complete
completeStep successfully completed
failedStep failed (may allow retry)
skippedStep skipped (workflow rules)

Case Statuses

StatusDescription
openCase created, awaiting assignment
in_progressCase assigned and being investigated
pending_infoWaiting for additional information
resolvedInvestigation complete
escalatedEscalated to senior reviewer
closedCase closed

Webhook Events by Status

EventTrigger
applicant.createdApplicant created (status: pending)
applicant.updatedAny applicant data change
applicant.reviewedStatus changed to approved/rejected
document.uploadedDocument uploaded (status: pending)
document.verifiedDocument processing complete
screening.completedScreening check finished
screening.hit_foundNew screening hit detected
case.createdNew case opened
case.resolvedCase resolved

Handling Status Changes

Polling vs. Webhooks

Polling (Not Recommended):

# Don't do this in production
while True:
applicant = get_applicant(applicant_id)
if applicant["status"] in ["approved", "rejected"]:
break
time.sleep(5)

Webhooks (Recommended):

@app.route("/webhooks/trustgate", methods=["POST"])
def handle_webhook():
event = request.json

if event["event"] == "applicant.reviewed":
applicant_id = event["data"]["applicant_id"]
status = event["data"]["status"]

if status == "approved":
grant_account_access(applicant_id)
elif status == "rejected":
send_rejection_email(applicant_id)

return {"status": "received"}, 200

Error Handling

def handle_verification_error(applicant_id, error_type):
if error_type == "screening_error":
# Retry screening
retry_screening(applicant_id)

elif error_type == "document_rejected":
# Notify user to re-upload
notify_user(
applicant_id,
"Please upload a clearer image of your document"
)

elif error_type == "biometric_failed":
# Allow retry or escalate
increment_biometric_attempts(applicant_id)
if get_biometric_attempts(applicant_id) >= 3:
create_manual_review_case(applicant_id)

Next Steps