> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usefini.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Fini for disputes and chargebacks

> How Fini handles dispute and fraud-claim intake (identifying the transaction, collecting structured details, freezing a card, opening the case through your API) while the dispute decision stays with your team.

export const TreeWalker = ({title = "Try it: run the example tree", nodes = [], scenarios = [], note}) => {
  const FV = {
    lime: "#C3EE5E",
    ink: "#131415",
    line: "rgba(127,127,127,0.28)",
    soft: "rgba(127,127,127,0.07)",
    softer: "rgba(127,127,127,0.04)",
    muted: "rgba(127,127,127,0.95)",
    pass: "#C3EE5E",
    warn: "#FFB020",
    fail: "#FF4D4D",
    radius: 14
  };
  const fvCard = {
    border: `1px solid ${FV.line}`,
    borderRadius: FV.radius,
    padding: 18,
    margin: "20px 0",
    background: FV.softer
  };
  const fvChip = active => ({
    border: `1px solid ${active ? FV.lime : FV.line}`,
    background: active ? FV.lime : "transparent",
    color: active ? FV.ink : "inherit",
    borderRadius: 999,
    padding: "6px 12px",
    fontSize: 13,
    fontWeight: 600,
    cursor: "pointer",
    lineHeight: 1.2
  });
  const fvBtn = primary => ({
    border: `1px solid ${primary ? FV.lime : FV.line}`,
    background: primary ? FV.lime : "transparent",
    color: primary ? FV.ink : "inherit",
    borderRadius: 10,
    padding: "7px 14px",
    fontSize: 13,
    fontWeight: 600,
    cursor: "pointer"
  });
  const fvLabel = {
    fontSize: 11,
    fontWeight: 700,
    letterSpacing: "0.08em",
    textTransform: "uppercase",
    opacity: 0.6,
    marginBottom: 8
  };
  const [sc, setSc] = useState(0);
  const [pos, setPos] = useState(-1);
  const [running, setRunning] = useState(false);
  const cur = scenarios[sc] || ({});
  const stop = typeof cur.stopAt === "number" ? cur.stopAt : null;
  const last = stop === null ? nodes.length - 1 : stop;
  useEffect(() => {
    if (!running) return;
    if (pos >= last) {
      setRunning(false);
      return;
    }
    const t = setTimeout(() => setPos(p => p + 1), 650);
    return () => clearTimeout(t);
  }, [running, pos, last]);
  const run = () => {
    setPos(-1);
    setRunning(true);
    setTimeout(() => setPos(0), 50);
  };
  const pick = k => {
    setSc(k);
    setPos(-1);
    setRunning(false);
  };
  const done = !running && pos >= last && pos >= 0;
  const typeColor = {
    Check: "#E8E8E8",
    Read: "#E8E8E8",
    Form: "#E8E8E8",
    Tool: "#E8E8E8",
    Reply: FV.lime
  };
  return <div style={fvCard}>
      <div style={fvLabel}>{title}</div>
      <div style={{
    display: "flex",
    gap: 8,
    flexWrap: "wrap",
    marginBottom: 14
  }}>
        {scenarios.map((s, k) => <button key={k} style={fvChip(k === sc)} onClick={() => pick(k)}>{s.name}</button>)}
      </div>
      <div style={{
    fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
    fontSize: 13.5
  }}>
        {nodes.map((n, k) => {
    const reached = pos >= k;
    const failed = done || running ? stop === k && reached : false;
    const skipped = stop !== null && k > stop && (done || running);
    const state = failed ? "fail" : reached ? "pass" : "idle";
    return <div key={k} style={{
      display: "flex",
      alignItems: "stretch",
      gap: 10,
      opacity: skipped ? 0.35 : 1,
      transition: "opacity .3s"
    }}>
              <div style={{
      width: 18,
      display: "flex",
      flexDirection: "column",
      alignItems: "center"
    }}>
                <div style={{
      width: 2,
      flex: 1,
      background: k === 0 ? "transparent" : FV.line
    }} />
                <div style={{
      width: 12,
      height: 12,
      borderRadius: 999,
      background: state === "fail" ? FV.fail : state === "pass" ? FV.pass : FV.line,
      transition: "background .3s"
    }} />
                <div style={{
      width: 2,
      flex: 1,
      background: k === nodes.length - 1 ? "transparent" : FV.line
    }} />
              </div>
              <div style={{
      flex: 1,
      margin: "4px 0",
      padding: "8px 12px",
      borderRadius: 10,
      border: `1px solid ${state === "fail" ? FV.fail : state === "pass" ? FV.pass : FV.line}`,
      background: state === "pass" ? "rgba(195,238,94,0.14)" : state === "fail" ? "rgba(255,77,77,0.08)" : "transparent",
      transition: "all .3s"
    }}>
                <span style={{
      fontSize: 11,
      fontWeight: 700,
      padding: "2px 8px",
      borderRadius: 6,
      marginRight: 10,
      background: typeColor[n.type] || FV.line,
      color: FV.ink
    }}>{n.type}</span>
                {n.label}
                {failed && n.onFail && <div style={{
      fontFamily: "inherit",
      fontSize: 12.5,
      marginTop: 6,
      color: FV.fail
    }}>Short-circuits: {n.onFail}</div>}
              </div>
            </div>;
  })}
      </div>
      <div style={{
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    marginTop: 14,
    gap: 10,
    flexWrap: "wrap"
  }}>
        <button style={fvBtn(true)} onClick={run} disabled={running}>{running ? "Running..." : done ? "Run again" : "Run scenario"}</button>
        {done && cur.outcome && <div style={{
    flex: 1,
    minWidth: 220,
    fontSize: 14,
    padding: "8px 12px",
    borderRadius: 10,
    border: `1px solid ${cur.escalates ? FV.warn : FV.pass}`
  }}>
            <b>{cur.escalates ? "Escalates: " : cur.resolved ? "Resolved: " : "Outcome: "}</b>{cur.outcome}
          </div>}
      </div>
      {note && <div style={{
    fontSize: 12,
    opacity: 0.6,
    marginTop: 10
  }}>{note}</div>}
    </div>;
};

Fini (usefini.com) handles the intake side of card disputes, chargebacks and fraud claims: it identifies the customer and the transaction, collects the dispute details in a structured Form, can freeze a compromised card through your API, opens the case in your dispute system through an Action, and replies with the case reference and the next steps your policy defines. The decision itself (whether the claim is valid, any provisional credit, the chargeback with the card network, and the final outcome) stays with your team and your systems, and Fini is configured so it never states or implies an outcome.

That split, intake versus decision, is the design principle for this use case. Intake is repetitive, structured and time-sensitive, which suits a deterministic Intent Rule. Decisions carry financial, regulatory and customer-rights consequences, which belong to your dispute operations team and the policies your compliance team approves.

```mermaid theme={null}
sequenceDiagram
    autonumber
    actor C as Customer
    participant F as Fini
    participant API as Your systems<br/>via Actions
    actor T as Your dispute team

    C->>F: Contests a transaction
    Note over F: Customer Identity loads the account and recent_transactions
    Note over F: Checks on intent and active account, Read resolves transaction_id
    F->>C: Form asks for the dispute details
    C->>F: dispute_reason, date_noticed, contacted_merchant, description
    F->>API: Create Dispute Case
    API-->>F: case_id, next_steps_summary
    F->>C: Confirms case_id and next steps, promises no outcome
    Note over T: Intake ends here. Decision stays with your team
    T->>C: Validity, provisional credit, chargeback and outcome
```

## What Fini handles and what it hands to your team

| Fini handles (intake) | Your team handles (decision) |
| - | - |
| Explaining how disputes work, what information is needed and what happens next, from Knowledge articles your compliance team approves. | Deciding whether the dispute or fraud claim is valid. |
| Identifying the customer and listing their recent transactions (User Attributes). | Provisional credit decisions and timing. |
| Working out which transaction the customer means, and asking when it's ambiguous. | Filing and managing chargebacks with the card network, and any merchant representment. |
| Collecting the dispute reason, the date the customer noticed, and a description, as a typed Form submission. | Communicating the outcome of the investigation to the customer. |
| Freezing a card the customer reports as compromised, through your card API. | Anything involving suspected identity theft or account takeover, by your Escalation Topics. |
| Opening the dispute case through an Action and confirming the case reference. | Customers who disagree with an outcome, complaints, and regulator or ombudsman references. |

## How it works in Fini

Dispute intake uses the same building blocks as the [Card replacement walkthrough](/en/walkthroughs/card-replacement), which is the closest end-to-end recipe: a chained User Attribute, a Form for structured input, chained Tools, and a Reply Rules gate for risk.

| Building block | What it does for disputes | Where |
| - | - | - |
| **Tags** | A custom tag group (the example uses `Topic`) with **Tag Group available in Rulebooks** on and separate tags for `disputes` (the customer contests a transaction) and `fraud_claim` (the customer says they didn't make it). Default groups such as **Type of Issue** have a locked tag list, so they can't take a tag like `fraud_claim`. The "when not to apply" section of each tag matters: a refund request for a known purchase is not a dispute. | [Tags](/en/configuration/tags) |
| **User Attributes** | A `Customer Identity` attribute with two chained Data Collection Steps: look up the customer (`account_id`, `account_status`, `is_high_risk`), then list recent transactions (`recent_transactions`). Turn on **Visible to AI** for the transaction list so the agent can ask which one the customer means; keep risk flags off it. | [Attributes](/en/api-reference/attributes) |
| **Actions** | `Create Dispute Case` (writes to your dispute or case-management system), and for fraud claims, `Freeze Card`. Mark inputs `required` so the Tool node can't fire without them. | [Actions](/en/api-reference/actions) |
| **Intent Rule** | Gates on intent and account state, resolves the transaction, renders the Form, opens the case and confirms the reference. | [Intent Rules](/en/automations/rulebook) |
| **Form** | Collects typed, validated fields (field types such as `String`, `Number` and `Date` drive input validation). The submission appears in the transcript as a structured record of what the customer provided. | [Intent Rules](/en/automations/rulebook#a-worked-example-address-change-with-form) |
| **Reply Rules** | **Internal Comment** with `Topic Equals disputes`, so the confirmation is reviewed before the customer sees it. Or **No Reply** for dispute types you want handled only by humans. | [Reply Rules](/en/automations/reply-behavior) |
| **Prompts** | The Planning Prompt's *Escalation Topics* for patterns that should go straight to a human ("fraud dispute" is a documented example), and **Predefined Replies** in Main Guidelines for wording your compliance team requires verbatim. | [Prompts](/en/configuration/prompts) |
| **Guardrails** | A **Custom rule** that fails any reply implying a dispute outcome, a refund, or a timeline the rule output didn't provide. **Confidential attributes** for fields such as internal case notes. | [Guardrails](/en/configuration/guardrails) |

### Example behavior tree

This is an example to adapt. Field names, tag values and the Form fields depend on your dispute system and the information your team needs to open a case.

```text theme={null}
Steps  (root)
├── Check: Topic Equals disputes
├── Check: account_status Equals active
├── Read:  resolve `transaction_id` (String) from the message and recent_transactions
├── Form:  "Tell us about this transaction"
│     ├── Field: dispute_reason (String, required)
│     ├── Field: date_noticed (Date, required)
│     ├── Field: contacted_merchant (String, required)
│     ├── Field: description (String, required)
│     └── Form Error: description fewer than 20 characters
├── Tool:  Create Dispute Case
│     in:  account_id, transaction_id, dispute_reason,
│          date_noticed, contacted_merchant, description
│     out: case_id, next_steps_summary
└── Reply: confirm case_id; explain next_steps_summary;
           do not promise an outcome, a credit or a date
```

<TreeWalker
  title="Try it: run the example dispute intake tree"
  nodes={[
{ type: "Check", label: "Topic Equals disputes", onFail: "the Steps short-circuits and the rule does nothing on this message" },
{ type: "Check", label: "account_status Equals active", onFail: "the rule stops before anything is written" },
{ type: "Read", label: "resolve transaction_id from the message and recent_transactions" },
{ type: "Form", label: "Tell us about this transaction: dispute_reason, date_noticed, contacted_merchant, description" },
{ type: "Tool", label: "Create Dispute Case", onFail: "the Steps fails, nothing is staged, and the agent falls through to its default reply" },
{ type: "Reply", label: "confirm case_id and explain next_steps_summary, with no outcome, credit or date promised" }
]}
  scenarios={[
{ name: "Contested charge, active account", outcome: "The case opens in your system and the reply confirms case_id and the next steps your team defined. The decision on the dispute stays with your team." },
{ name: "Refund for a recognized purchase", stopAt: 0, outcome: "The intent Check fails, so the rule does nothing on this message. A refund request for a known purchase belongs to your refund flow (see Refunds and returns), not dispute intake." },
{ name: "Case creation fails", stopAt: 4, outcome: "No case reference is given and the rule stages no Reply, so the agent falls through to its default reply. If the conversation escalates, Analytics records the reason as API or System Failure. To guarantee a handoff here, wrap the Tool and Reply in a Fallback whose second child is a Reply saying a specialist will follow up." }
]}
  note="Example tree to adapt, not a default configuration. Fraud claims use a separate rule that calls Freeze Card first."
/>

How it runs:

1. Two Checks gate the rule on intent and on an active account before anything is written.
2. The Read picks the transaction the customer described from `recent_transactions`. If it can't pick exactly one, wrap the rest of the tree in a Fallback whose second child is a Reply listing the candidates, the disambiguation pattern from the [Card replacement walkthrough](/en/walkthroughs/card-replacement).
3. The Form collects the details as typed fields, with Form Error children for validation. Forms render in the [widget](/en/deploy/widget); on email-only channels, replace the Form with a sequence of Reads and Replies, as the card replacement walkthrough's channel notes describe.
4. `Create Dispute Case` opens the case in your system and returns its reference. `next_steps_summary` comes from your system, so the agent describes the next steps your team defined rather than composing its own.

For fraud claims, a separate rule (or a sibling branch under a Fallback root) gates on `fraud_claim`, calls `Freeze Card` first, and then tells the customer the card is frozen and a specialist will follow up. The card replacement walkthrough explains why freezing is a safe first step: it's reversible, and it stops further use while your team investigates.

## What you need

| What | Why | Where |
| - | - | - |
| **An authenticated channel** | Disputes act on a payment instrument. Identify the customer with a signed JWT in the widget, or through the helpdesk's authenticated user. On unauthenticated channels, add an identity check before any Tool fires (see the identity verification note in the [Card replacement walkthrough](/en/walkthroughs/card-replacement)). | [Widget](/en/deploy/widget) |
| **Customer and transaction lookup endpoints** | Read-only. Feed the `Customer Identity` attribute. | Your core banking, ledger or card processor API |
| **A case-creation endpoint** | Writes the dispute case and returns a reference and next-step text. Should accept an idempotency key so a repeated message doesn't open two cases. | Your dispute or case-management system |
| **A card freeze endpoint** (fraud claims) | Freezes the card and returns a confirmation id. | Your card processor or card-management API |
| **Credentials scoped to these operations** | Stored in the Data Step **Headers** JSON (for example an `Authorization` header). Endpoints you build should follow the [API contract](/en/api-reference/api-contract). | [Actions](/en/api-reference/actions) |
| **Approved dispute articles and wording** | The agent explains the process from Knowledge; compliance-required phrases go in **Predefined Replies**. | [Knowledge](/en/knowledge/overview), [Prompts](/en/configuration/prompts) |
| **A sandbox** | Test Suite runs use recorded or mock Action responses, so they never create real cases or freeze real cards. For a live end-to-end check, point the Actions at a sandbox environment or run a controlled pilot. | [Test Suite](/en/testing/test-suite) |

## Guardrails and escalation

Disputes are where you should choose the most conservative pattern your team can operate, and loosen it once you have evidence from production.

| Pattern | What happens | When to use it | How to configure it |
| - | - | - | - |
| **Intake, then hand off** | The rule opens the case, and the reply tells the customer a specialist will follow up. | Most card disputes. The customer gets a reference immediately; your team owns the decision. | The example tree. The final Reply confirms the case and tells the customer a specialist will follow up, which hands the conversation to your team. |
| **Draft for review** | The rule runs and opens the case, but the reply posts as an internal note. A human sends the customer-facing message. | When every customer-facing dispute message must be reviewed. | **Internal Comment** card: `Topic Equals disputes`. |
| **Human only** | The agent doesn't engage on the conversation. | Dispute types your policy reserves for people, or high-risk customers. | **No Reply** card, for example `Topic Equals fraud_claim` AND `is_high_risk Equals True`, or an *Escalation Topics* entry in the Planning Prompt. |
| **Hold the write itself** | The rule collects details and posts an internal note; a human opens the case. | When opening a case is itself a step your team must authorize. | Split into two rules, the pattern described under "Holding the destructive Action itself" in the [Card replacement walkthrough](/en/walkthroughs/card-replacement). |

<Warning>
  **Reply Rules don't stop Tools.** An Internal Comment rule changes who sees the reply. The Tools in the Intent Rule (case creation, card freeze) still run. If a step must not happen without a human, keep it out of the automated tree.
</Warning>

### Regulated steps

Card and electronic-payment disputes are often subject to regulated timelines and notice requirements, which vary by jurisdiction, product and card network. Fini doesn't know or apply those timelines on its own. Your compliance team defines them; you encode the customer-facing parts in approved Knowledge articles and **Predefined Replies**, and keep timeline-bearing decisions (acknowledgment, provisional credit, final outcome) with your team and systems. For regulated steps, use the internal-note or human-only patterns above, and add a Main Guidelines rule such as *"Never state whether a dispute will succeed, whether a credit will be issued, or by when, unless the value comes from the rule output."*

Customers sometimes paste a full card number into a dispute message, so design the flow so they never need to. Your Forms should never ask for a full card number or CVV; identify the transaction from your own records and route card actions through your payment provider via [Actions](/en/api-reference/actions). Fini automatically masks sensitive data, including card numbers and health details, everywhere it stores conversation data (transcripts, Inbox and **AI Steps** traces). Fields you hide from the AI are also redacted in **AI Steps**. Guardrails remain an extra layer on what the agent sends. See [Data handling](/en/security/data-handling) and [PCI DSS](/en/security/pci-dss).

Other escalation signals to configure:

* **Escalation Topics** in the Planning Prompt for identity theft, account-takeover signals and threats of legal action or regulator complaints (the default legal and regulatory trigger already covers the last).
* **No Reply** with `Human Agent Assigned Equals True`, so the agent doesn't talk over a specialist who has picked up the case.
* **Guardrails** with a **Custom rule** for outcome promises, and **Banned terms** for phrases your team never uses about disputes.

## What to measure

Scope [Analytics](/en/analytics) to your dispute and fraud tags with the **Tags** filter, or to the dispute rule with the **Intent rule** filter.

| Metric | Where | What it tells you |
| - | - | - |
| AI resolution rate for the intent | **Conversation Status** doughnut, share **Resolved by AI** | How often intake finishes without a human. With the intake-then-handoff pattern, many dispute conversations are expected to end **Escalated to Human Team** by design, so read this alongside your pattern choice. |
| AI Resolve Rate and Escalated Rate | **Intent rule breakdown** row for the dispute rule | Whether the rule reaches case creation or leaks before it. |
| Human escalation rate | KPI card, with the filter applied | Your team's dispute workload arriving from the agent. |
| Escalation reasons | **Escalation reason** filter | **API or System Failure** means case creation or lookup failed; **Ambiguous or Unclear Input** means transaction resolution needs work; **Escalation Constraint** is your policy. |
| CSAT | **Average CSAT** and the **CSAT** filter | How customers felt about intake, separate from the eventual outcome. |

## Related

<CardGroup cols={2}>
  <Card title="Card replacement" icon="credit-card" href="/en/walkthroughs/card-replacement">
    Step-by-step fintech build with chained attributes, a Form, chained Tools and a Reply Rules risk gate.
  </Card>

  <Card title="Reply Rules" icon="turn-down-right" href="/en/automations/reply-behavior">
    Internal Comment and No Reply for sensitive intents, and the priority order.
  </Card>

  <Card title="Prompts" icon="message" href="/en/configuration/prompts">
    Escalation Topics in the Planning Prompt and Predefined Replies in Main Guidelines.
  </Card>

  <Card title="Guardrails" icon="shield-check" href="/en/configuration/guardrails">
    Reply checks that rewrite or escalate replies before delivery.
  </Card>

  <Card title="Escalation and handoff" icon="headset" href="/en/use-cases/escalation-and-handoff">
    How the handoff reaches your team on each surface, and what context comes with it.
  </Card>

  <Card title="Refunds and returns" icon="money-bill-transfer" href="/en/use-cases/refunds">
    For refund requests on purchases the customer recognizes.
  </Card>
</CardGroup>

For a comparison of AI platforms for fintech dispute handling, see the guide on [usefini.com](https://www.usefini.com/guides/best-ai-platforms-fintech-dispute-resolution).


## Related topics

- [Setting up Fini for fintech and banking](/en/industry-setup/fintech.md)
- [Fini for billing and invoices](/en/use-cases/billing-and-invoices.md)
- [PCI DSS](/en/security/pci-dss.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.