How to Build a KYC verification Agent Using CrewAI in TypeScript for payments

By Cyprian AaronsUpdated 2026-04-21
kyc-verificationcrewaitypescriptpayments

A KYC verification agent for payments takes customer identity data, checks it against policy and external sources, and returns a decision with evidence. In practice, it reduces manual review load while keeping onboarding fast enough for card issuance, wallets, and payout flows where compliance and fraud risk are non-negotiable.

Architecture

  • Intake layer

    • Accepts user-submitted KYC payloads: name, DOB, address, document metadata, jurisdiction, and consent flags.
    • Normalizes fields before the agent sees them.
  • Verification tools

    • Document validation tool for passport/ID checks.
    • Watchlist/sanctions lookup tool.
    • Address and phone/email validation tool.
    • Optional liveness or biometric signal verifier if your product supports it.
  • CrewAI agent

    • One primary Agent that reasons over policy and evidence.
    • One or more specialized agents if you want separation between document review and compliance review.
  • Workflow orchestration

    • A Crew with sequential tasks:
      • collect evidence
      • assess risk
      • produce decision
    • Deterministic output schema for downstream payment systems.
  • Decision store

    • Persist the final verdict, rationale, timestamps, source references, and reviewer overrides.
    • This is what audit teams will ask for later.

Implementation

1) Install dependencies and define your KYC contract

Use CrewAI’s TypeScript package plus a schema validator. Keep the output strict; payment systems should not ingest free-form text as a decision.

npm install @crewai/crewai zod
// kyc.types.ts
import { z } from "zod";

export const KycInputSchema = z.object({
  customerId: z.string(),
  fullName: z.string(),
  dateOfBirth: z.string(),
  country: z.string(),
  documentType: z.enum(["passport", "national_id", "driver_license"]),
  documentNumber: z.string(),
  email: z.string().email(),
  phone: z.string(),
  consentGiven: z.boolean(),
});

export const KycDecisionSchema = z.object({
  status: z.enum(["approved", "manual_review", "rejected"]),
  riskScore: z.number().min(0).max(100),
  reasons: z.array(z.string()),
  evidenceRefs: z.array(z.string()),
});

export type KycInput = z.infer<typeof KycInputSchema>;
export type KycDecision = z.infer<typeof KycDecisionSchema>;

2) Wrap your compliance checks as tools

For payments, the agent should not “guess” about sanctions or identity validity. It should call deterministic tools that hit your approved providers or internal services.

// kyc.tools.ts
import { Tool } from "@crewai/crewai";

export const sanctionsLookupTool = new Tool({
  name: "sanctions_lookup",
  description: "Checks a customer against sanctions and watchlist sources.",
  async execute(input: string) {
    const payload = JSON.parse(input) as { fullName: string; country: string };
    // Replace with your sanctioned vendor / internal API call.
    const matched = payload.fullName.toLowerCase().includes("test");
    return JSON.stringify({
      matched,
      source: "internal_watchlist_service",
      referenceId: `watch-${Date.now()}`,
    });
  },
});

export const documentValidationTool = new Tool({
  name: "document_validation",
  description: "Validates identity document metadata and format.",
  async execute(input: string) {
    const payload = JSON.parse(input) as { documentType: string; documentNumber: string };
    const validFormat = payload.documentNumber.length >= 8;
    return JSON.stringify({
      validFormat,
      source: "doc_validator",
      referenceId: `doc-${Date.now()}`,
    });
  },
});

3) Build the CrewAI agent and task flow

This is the core pattern. The agent gets policy instructions, the tools above, and a task that forces structured output. Use Agent, Task, and Crew directly.

// kyc.agent.ts
import { Agent, Crew, Task } from "@crewai/crewai";
import { KycInputSchema } from "./kyc.types";
import { sanctionsLookupTool, documentValidationTool } from "./kyc.tools";

const kycAgent = new Agent({
  role: "KYC Compliance Analyst",
  goal:
    "Assess customer identity data for payment onboarding using policy-compliant evidence only.",
  backstory:
    "You review identity checks for regulated payment products. You must be conservative when evidence is incomplete.",
  tools: [sanctionsLookupTool, documentValidationTool],
});

const kycTask = new Task({
  description:
    "Review this KYC payload. Check sanctions/watchlists and document validity. Return only JSON with status, riskScore, reasons, evidenceRefs.",
});

export async function verifyKyc(rawInput: unknown) {
  const input = KycInputSchema.parse(rawInput);

   const crew = new Crew({
    agents: [kycAgent],
    tasks: [kycTask],
    verbose: true,
   });

   const result = await crew.kickoff({
     input,
     context: {
       compliancePolicy:
         "Reject if sanctions match is true. Manual review if any required field is missing or doc validation fails.",
     },
   });

   return result;
}

4) Add a payment-safe wrapper around the agent result

Do not pass the raw model output straight into onboarding or payouts. Validate it again before writing to your ledger or case management system.

// index.ts
import { KycDecisionSchema } from "./kyc.types";
import { verifyKyc } from "./kyc.agent";

async function main() {
  const decisionRaw = await verifyKyc({
    customerId: "cus_123",
    fullName: "Jane Doe",
    dateOfBirth: "1992-04-10",
    country: "KE",
    documentType: "passport",
    documentNumber: "P12345678",
    email: "jane@example.com",
    phone: "+254700000000",
    consentGiven: true,
  });

  const decision = KycDecisionSchema.parse(decisionRaw);

  console.log("KYC decision:", decision);
}

main().catch((err) => {
  console.error(err);
});

Production Considerations

  • Auditability

    • Persist every tool call, prompt version, model version, final decision, and human override.
    • For payments audits, you need an immutable trail tied to customerId and case ID.
  • Data residency

    • Keep PII inside the required region.
    • If your payment stack is EU-only or country-specific, route LLM calls through region-bound infrastructure and avoid sending raw documents across borders.
  • Guardrails

**
Wait** – let's continue correctly:

  • Guardrails

Oops — no broken formatting in production docs; keep it simple:

  • Guardrails

Actually use this:

  • Guardrails

Let's finalize properly:

Production Considerations

  • Auditability

Keep learning

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

Related Guides