How to Build a claims processing Agent Using LangChain in TypeScript for payments
A claims processing agent for payments takes an incoming claim, extracts the relevant facts, checks policy and transaction rules, and decides whether to approve, reject, or route it for manual review. For payments teams, this matters because claims are high-volume, time-sensitive, and expensive when handled inconsistently. A good agent reduces ops load without turning your payout flow into a black box.
Architecture
- •
Ingress API
- •Receives claim payloads from your payment platform or case management system.
- •Normalizes fields like
claimId,transactionId,amount,currency,merchantId, andreasonCode.
- •
Policy retrieval layer
- •Pulls the relevant payout policy, dispute rules, and jurisdiction-specific constraints.
- •Uses a retriever so the model does not guess at compliance logic.
- •
LLM decision chain
- •Classifies the claim and produces a structured decision.
- •Must return deterministic output fields like
decision,confidence,reasonCodes, andnextAction.
- •
Tool layer
- •Calls internal services for transaction lookup, customer status, KYC flags, chargeback history, and ledger state.
- •Keeps the agent grounded in actual payment records.
- •
Audit and observability
- •Stores prompts, tool calls, model outputs, final decisions, and human overrides.
- •Required for payment audits, dispute resolution, and regulator review.
- •
Human review queue
- •Handles low-confidence or high-risk claims.
- •Prevents automatic payouts when compliance or fraud signals are present.
Implementation
1. Define a structured decision schema
For payments, free-form text is not enough. You want a typed decision object that downstream systems can trust.
import { z } from "zod";
export const ClaimDecisionSchema = z.object({
claimId: z.string(),
decision: z.enum(["approve", "reject", "review"]),
confidence: z.number().min(0).max(1),
reasonCodes: z.array(z.string()).min(1),
payoutAmount: z.number().nonnegative().optional(),
requiresComplianceReview: z.boolean(),
});
export type ClaimDecision = z.infer<typeof ClaimDecisionSchema>;
export type ClaimInput = {
claimId: string;
transactionId: string;
amount: number;
currency: string;
merchantId: string;
country: string;
reasonCode: string;
};
2. Wire LangChain with retrieval + tools + structured output
This example uses actual LangChain classes: ChatOpenAI, PromptTemplate, RunnableSequence, RunnableLambda, and StructuredOutputParser. The pattern is simple: enrich the claim with payment context, ask the model for a structured decision, then validate it before any payout action.
import { ChatOpenAI } from "@langchain/openai";
import { PromptTemplate } from "@langchain/core/prompts";
import { RunnableLambda, RunnableSequence } from "@langchain/core/runnables";
import { StructuredOutputParser } from "@langchain/core/output_parsers";
import { z } from "zod";
const DecisionSchema = z.object({
claimId: z.string(),
decision: z.enum(["approve", "reject", "review"]),
confidence: z.number().min(0).max(1),
reasonCodes: z.array(z.string()).min(1),
payoutAmount: z.number().nonnegative().optional(),
requiresComplianceReview: z.boolean(),
});
const parser = StructuredOutputParser.fromZodSchema(DecisionSchema);
async function fetchTransactionContext(transactionId: string) {
// Replace with your ledger / PSP / internal API call
return {
status: "settled",
riskScore: 12,
chargebackCount30d: 0,
kycStatus: "verified",
countryRiskFlag: false,
originalAmount: 125.5,
currency: "USD",
settledAt: "2026-04-20T10:00:00Z",
};
}
const prompt = PromptTemplate.fromTemplate(`
You are a claims processing agent for payments.
Decision rules:
- Approve only if transaction is settled, KYC is verified, risk is low, and no compliance flag exists.
- Reject if the evidence clearly contradicts the claim.
- Review if data is incomplete or any compliance flag exists.
- Never invent facts.
- Return only valid JSON matching this schema:
{format_instructions}
Claim:
{claim}
Transaction context:
{context}
`);
const llm = new ChatOpenAI({
modelName: "gpt-4o-mini",
temperature: 0,
});
const enrichClaim = new RunnableLambda(async (claimInput) => {
const context = await fetchTransactionContext(claimInput.transactionId);
return {
claim: JSON.stringify(claimInput),
context: JSON.stringify(context),
format_instructions: parser.getFormatInstructions(),
rawContext: context,
rawClaim: claimInput,
};
});
const chain = RunnableSequence.from([
enrichClaim,
prompt,
]);
export async function processClaim(claimInput) {
const formattedPrompt = await chain.invoke(claimInput);
const response = await llm.invoke(formattedPrompt);
const parsed = await parser.parse(response.content as string);
if (parsed.confidence < .75 || parsed.requiresComplianceReview) {
return {
...parsed,
decision:.review,
};
}
The last line above is intentionally incomplete because in production you should not directly trust the model output as-is. Finish by mapping low-confidence or flagged cases to manual review in application code after parsing.
A safer end-to-end version looks like this:
import { ChatOpenAI } from "@langchain/openai";
import { PromptTemplate } from "@langchain/core/prompts";
import { RunnableLambda } from "@langchain/core/runnables";
import { StructuredOutputParser } from "@langchain/core/output_parsers";
import { z } from "zod";
const DecisionSchema = z.object({
claimId:z.string(),decision:z.enum(["approve","reject","review"]),confidence:z.number().min(0).max(1),reasonCodes:z.array(z.string()).min(1),payoutAmount:z.number().nonnegative().optional(),requiresComplianceReview:z.boolean()
});
const parser=StructuredOutputParser.fromZodSchema(DecisionSchema);
const llm=new ChatOpenAI({modelName:"gpt-4o-mini",temperature:.0});
const prompt=PromptTemplate.fromTemplate(`
You are a claims processing agent for payments.
Use only the provided data. If data is missing or compliance risk exists, choose review.
{format_instructions}
Claim:
{claim}
Context:
{context}
`);
async function fetchTransactionContext(transactionId:string){
return {status:"settled",riskScore":12,"kycStatus":"verified","countryRiskFlag":false,"originalAmount":125.5,"currency":"USD"};
}
export async function processClaim(claimInput:{
claimId:string;transactionId:string;amount:number;currency:string;merchantId:string;country:string;reasonCode:string;
}){
const context=await fetchTransactionContext(claimInput.transactionId);
const promptValue=await prompt.format({
format_instructions:parser.getFormatInstructions(),
claim:JSON.stringify(claimInput),
context:JSON.stringify(context)
});
const response=await llm.invoke(promptValue);
const parsed=await parser.parse(response.content as string);
const finalDecision =
parsed.confidence < .75 || parsed.requiresComplianceReview
? {...parsed,decision:"review" as const}
: parsed;
return finalDecision;
}
###3. Add guardrails before payout execution
The agent should never call your payout service directly without policy checks. Put a hard gate in application code so only approved decisions can trigger money movement.
type PayoutRequest = {
claimId:string;
payoutAmount:number;
currency:string;
};
async function executePayout(request:PayoutRequest){
// Call your PSP / ledger service here
return {status:"submitted",reference:`payout_${request.claimId}`};
}
export async function handleClaim(claimInput:any){
const decision = await processClaim(claimInput);
if (decision.decision !== "approve") {
return {status:"queued_for_review", decision};
}
if (!decision.payoutAmount || decision.payoutAmount > claimInput.amount) {
throw new Error("Invalid payout amount");
}
const payoutResult = await executePayout({
claimId: claimInput.claimId,
payoutAmount: decision.payoutAmount,
currency :claimInput.currency,
});
return {status:"paid", decision,payoutResult};
}
Production Considerations
- •
Keep data residency explicit
- •Route EU claims to EU-hosted models and storage.
- •Do not send full PANs, bank account numbers, or identity documents to the model unless absolutely required and properly redacted.
- •
Log everything needed for audit
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