How to Build a KYC verification Agent Using LangGraph in TypeScript for healthcare
A KYC verification agent for healthcare collects identity data, checks it against policy and external systems, and decides whether a patient, caregiver, or provider can proceed. In healthcare, this matters because bad identity handling creates fraud risk, breaks compliance, and can expose protected health information to the wrong workflow.
Architecture
- •
Input normalization node
- •Cleans up user-submitted identity fields like name, DOB, address, phone, and government ID.
- •Validates required fields before any external calls.
- •
Document verification node
- •Checks uploaded IDs, insurance cards, or authorization forms.
- •Extracts metadata and flags missing or inconsistent documents.
- •
Policy and compliance node
- •Applies healthcare-specific rules such as minimum required identifiers, consent checks, and residency constraints.
- •Blocks processing if the request violates data handling policy.
- •
External verification node
- •Calls third-party identity services or internal master patient index systems.
- •Confirms the identity against trusted records.
- •
Risk scoring node
- •Produces an approval, manual review, or rejection decision.
- •Uses deterministic thresholds so audit trails are explainable.
- •
Audit sink
- •Stores every state transition, decision input, and output.
- •Keeps a trace for compliance review and incident response.
Implementation
1) Define the graph state and the workflow shape
Use a typed state object so every node reads and writes predictable fields. In healthcare workflows, this is not optional; you need stable auditability around what was collected and why a decision was made.
import { Annotation, StateGraph, START, END } from "@langchain/langgraph";
type KycDecision = "approved" | "manual_review" | "rejected";
const KycState = Annotation.Root({
input: Annotation<{
fullName: string;
dob: string;
address?: string;
nationalId?: string;
consentGiven: boolean;
residencyCountry: string;
}>(),
normalized: Annotation<Record<string, unknown>>({
default: () => ({}),
}),
documentChecks: Annotation<{
valid: boolean;
issues: string[];
}>({
default: () => ({ valid: false, issues: [] }),
}),
riskScore: Annotation<number>({
default: () => 0,
}),
decision: Annotation<KycDecision | null>({
default: () => null,
}),
});
2) Add nodes for normalization, policy checks, and decisioning
Keep the policy logic explicit. Healthcare teams need to explain why a case was blocked without reverse-engineering model output later.
const normalizeInput = async (state: typeof KycState.State) => {
const input = state.input;
return {
normalized: {
fullName: input.fullName.trim().toLowerCase(),
dob: input.dob,
address: input.address?.trim(),
nationalId: input.nationalId?.replace(/\s+/g, ""),
consentGiven: input.consentGiven,
residencyCountry: input.residencyCountry.toUpperCase(),
},
};
};
const applyHealthcarePolicy = async (state: typeof KycState.State) => {
const n = state.normalized as Record<string, unknown>;
const issues: string[] = [];
if (!n.consentGiven) issues.push("Missing patient consent");
if (!n.fullName || !n.dob) issues.push("Missing core identity fields");
if ((n.residencyCountry as string) !== "US") {
issues.push("Data residency requires manual review for non-US records");
}
return {
documentChecks: {
valid: issues.length === 0,
issues,
},
riskScore: issues.length * 40,
decision:
issues.length === 0 ? "manual_review" : "rejected",
};
};
const finalDecision = async (state: typeof KycState.State) => {
const score = state.riskScore;
const docs = state.documentChecks;
let decision: KycDecision = "manual_review";
if (docs.valid && score < 20) decision = "approved";
if (!docs.valid && score >= 40) decision = "rejected";
return { decision };
};
3) Wire the graph with StateGraph and compile it
This is the actual LangGraph pattern in TypeScript. The graph is small here on purpose; in production you would split document extraction and external verification into separate nodes with retries and timeouts.
const graph = new StateGraph(KycState)
.addNode("normalizeInput", normalizeInput)
.addNode("applyHealthcarePolicy", applyHealthcarePolicy)
.addNode("finalDecision", finalDecision)
graph.addEdge(START, "normalizeInput");
graph.addEdge("normalizeInput", "applyHealthcarePolicy");
graph.addEdge("applyHealthcarePolicy", "finalDecision");
graph.addEdge("finalDecision", END);
const app = graph.compile();
4) Invoke the agent with a real payload
Use invoke() for a single verification request. For batch operations or streaming review UIs, you can switch to stream() later without changing the core graph logic.
const result = await app.invoke({
input: {
fullName: "Jane Doe",
dob: "1991-04-12",
address: "10 Main Street",
nationalId: "A12345678",
consentGiven: true,
residencyCountry: "US",
},
});
console.log({
decision: result.decision,
});
Production Considerations
- •
Encrypt PHI at rest and in transit
- •Do not log raw identity documents or medical identifiers.
- •
Pin data residency by region
- •Keep patient verification data inside approved jurisdictions.
- •
Add audit trails for every node transition
- •Persist inputs, outputs, timestamps, and policy reasons for each step.
- •
Put human review behind deterministic thresholds
- •Use manual review for ambiguous cases instead of letting an LLM make final compliance decisions.
Common Pitfalls
- •
Letting an LLM decide compliance outcomes
- •Avoid this by keeping approval/rejection rules deterministic in code.
- •Use model output only for extraction or classification support.
- •
Logging sensitive healthcare identifiers
- •Avoid storing raw DOBs, national IDs, or insurance numbers in application logs.
- •Redact before logging and send full records only to approved secure stores.
- •
Skipping residency checks
- •Healthcare workflows often have strict regional storage requirements.
- •Enforce residency before calling external vendors or writing to downstream systems.
- •
Building one giant node
- •Don’t cram normalization, policy checks, vendor calls, and scoring into one function.
- •Split them into nodes so you can test each rule independently and trace failures cleanly.
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
By Cyprian Aarons, AI Consultant at Topiax.
Want the complete 8-step roadmap?
Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.
Get the Starter Kit