How to Build a transaction monitoring Agent Using LangGraph in TypeScript for fintech

By Cyprian AaronsUpdated 2026-04-21
transaction-monitoringlanggraphtypescriptfintech

A transaction monitoring agent watches payment events, scores them against policy and risk signals, and decides whether to auto-clear, enrich, escalate, or file for review. For fintech, this matters because you need fast decisions without losing auditability, compliance traceability, or control over false positives.

Architecture

  • Event intake layer

    • Receives card payments, ACH transfers, wallet top-ups, and internal ledger movements.
    • Normalizes raw payloads into a common TransactionEvent shape.
  • Feature enrichment node

    • Pulls customer profile data, merchant category codes, device fingerprints, velocity counters, and sanctions/PEP flags.
    • Keeps enrichment deterministic and logged for audit.
  • Risk scoring node

    • Applies rules plus model outputs to produce a risk score and reason codes.
    • Outputs structured decisions like APPROVE, REVIEW, BLOCK, or ESCALATE.
  • Policy/controls node

    • Enforces compliance rules: KYC status, jurisdiction constraints, threshold limits, and suspicious activity triggers.
    • Separates model judgment from hard controls.
  • Case management node

    • Creates an alert or case record when the transaction needs human review.
    • Stores evidence for investigators and regulators.
  • Audit trail store

    • Persists every node input/output, decision path, and timestamps.
    • Required for explainability and regulatory defensibility.

Implementation

1. Define the state and decision contract

Use a typed state object so each node only reads and writes what it owns. In fintech systems, the state should carry enough context for audit without leaking unnecessary PII.

import { Annotation } from "@langchain/langgraph";

export type TransactionEvent = {
  transactionId: string;
  customerId: string;
  amount: number;
  currency: string;
  country: string;
  merchantCategory: string;
};

export type RiskDecision = "APPROVE" | "REVIEW" | "BLOCK" | "ESCALATE";

export const MonitoringState = Annotation.Root({
  event: Annotation<TransactionEvent>(),
  enrichment: Annotation<{
    kycStatus: "PASS" | "FAIL";
    velocity24h: number;
    sanctionsHit: boolean;
    pepHit: boolean;
  }>(),
  score: Annotation<number>(),
  decision: Annotation<RiskDecision>(),
  reasons: Annotation<string[]>(),
});

2. Build the graph with explicit nodes

LangGraph in TypeScript gives you a clean way to model this as a graph of deterministic steps. Keep your nodes small and side-effect aware; anything that writes to case management or audit storage should be isolated.

import { StateGraph, START, END } from "@langchain/langgraph";

const enrichTransaction = async (state: typeof MonitoringState.State) => {
  const { event } = state;

  // Replace with real lookups to KYC/AML services
  return {
    enrichment: {
      kycStatus: event.customerId.startsWith("cust_") ? "PASS" : "FAIL",
      velocity24h: event.amount > 5000 ? 7 : 1,
      sanctionsHit: event.country === "IR",
      pepHit: false,
    },
    reasons: [],
  };
};

const scoreRisk = async (state: typeof MonitoringState.State) => {
  const { event, enrichment } = state;
  let score = 0;
  const reasons: string[] = [];

  if (event.amount >= 10000) {
    score += 40;
    reasons.push("high_amount");
  }
  if (enrichment.sanctionsHit) {
    score += 100;
    reasons.push("sanctions_match");
  }
  if (enrichment.velocity24h >= 5) {
    score += 25;
    reasons.push("high_velocity");
  }
  if (enrichment.kycStatus === "FAIL") {
    score += 50;
    reasons.push("kyc_failed");
  }

   return { score, reasons };
};

const decide = async (state: typeof MonitoringState.State) => {
   const { score } = state;

   if (score >= 100) return { decision: "BLOCK" as const };
   if (score >=80) return { decision:"ESCALATE" as const };
   if (score >=40) return { decision:"REVIEW" as const };
   return { decision:"APPROVE" as const };
};

const graph = new StateGraph(MonitoringState)
   .addNode("enrichTransaction", enrichTransaction)
   .addNode("scoreRisk", scoreRisk)
   .addNode("decide", decide)
   .addEdge(START,"enrichTransaction")
   .addEdge("enrichTransaction","scoreRisk")
   .addEdge("scoreRisk","decide")
   .addEdge("decide",END)
   .compile();

3. Run the agent and persist the outcome

The compiled graph returns the final state after all nodes run. In production, wrap this in a service that stores the full trace in your audit log and emits an alert only when needed.

const result = await graph.invoke({
  event: {
    transactionId: "txn_123",
    customerId: "cust_456",
    amount:10_250,
    currency:"USD",
    country:"US",
    merchantCategory:"6012",
},
});

console.log({
 transactionId: result.event.transactionId,
 score: result.score,
 decision:key=>result.decision,
 reasons:user=>result.reasons,
});

What this pattern gives you

  • A deterministic flow from intake to decision.
  • A typed contract that is easy to test.
  • A place to insert human review before any irreversible action.
  • An audit-friendly chain of evidence for regulators.

Production Considerations

  • Keep data residency boundaries explicit

    • If your customers are in the EU or a specific banking region, run enrichment and storage in-region.
    • Don’t send raw PII or transaction payloads to external model endpoints unless your legal/compliance team has approved it.
  • Log every decision path

    • Store input features, rule hits, scores, final decisions, and timestamps.
    • Investigators need to answer “why was this blocked?” without reconstructing it from application logs.
  • Separate hard controls from soft scoring

    • Sanctions hits, blocked geographies, and KYC failures should override model scores.
  • Add guardrails around human escalation

Common Pitfalls

  • Using an LLM as the primary risk engine

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