Before you start
This walkthrough configures the Behavior layer of your Fini agent (the Rulebook tree, the two chained Actions, the Reply Behavior fraud gate). It assumes the other three layers your agent needs are already in place: Knowledge so the agent has substance for the fall-through paths (closed accounts, frozen-but-not-lost cards, security guidance), Prompts so the Reply nodes inherit your tone and your Planning Prompt’s escalation rules, and Deployment so customers can actually reach the agent. If you’re starting from scratch, run Quickstart first. Card replacement specifically also needs the following:What you’ll build
A card-replacement flow that:- Identifies the customer and their cards on every message via a single
Customer Identityattribute with two chained Data Collection Steps: step 1 looks up the customer (account_id,account_status,is_high_risk,recent_replacement_count); step 2 uses thataccount_idto list the customer’s active cards (active_cards). One attribute, two HTTP calls in dependency order. - Confirms which card to replace when the customer has more than one active card, by extracting a
card_last_fourfrom the message or asking via a quick disambiguation reply. - Collects a shipping address with a Form node so the address is typed, validated, and recorded as a structured artifact on the conversation transcript.
- Freezes the compromised card via a
Freeze CardAction, then orders a replacement via anOrder Replacement CardAction, two chained Tools, with the freeze’sconfirmation_idflowing into the order’s input. - Gates fraud-risk replacements for human review by routing high-risk customers or repeat-replacement requests through Reply Behavior’s Internal Comment.
Step 1: Configure the Customer Identity attribute
The agent needs two pieces of context on every message: who the customer is (account state, risk flags) and which cards they have (so the replacement Tool can address the right card). Both come from a single attribute with two Data Collection Steps chained in dependency order, step 2 references a field collected by step 1.
Create the attribute
Customer Identity. Pick a Source:widgetif your widget signs the customer’s identity into a JWT (the standard pattern for authenticated fintech, see Widget → Identify logged-in users for the payload shape).uiif your app passes the customer’s email through UI metadata.- The integration’s own name (
zendesk,intercom,front,gorgias,hubspot,salesforce,livechat) if you’re routing through a helpdesk.
Declare the input field
email (or customer_token, whichever your widget signs) with Use as Input in API turned on. This is the lookup key step 1 will pass to your customer-management API.Add Data Collection Step 1: Lookup Customer
- Step Name:
Lookup Customer - Method:
GET - URL:
https://api.yourbank.com/v1/customers?email=${email} - Headers:
{"x-api-key": "your-cards-api-key"}(per Fini’s API contract when calling your own endpoint; use the third-party scheme if you’re calling a processor’s SaaS API directly). - Save From Response:
{"account_id": "id", "account_status": "status", "is_high_risk": "risk_flags.kyc_recheck_required", "recent_replacement_count": "stats.card_replacements_30d"}
Add Data Collection Step 2: List Active Cards
- Step Name:
List Active Cards - Method:
GET - URL:
https://api.yourbank.com/v1/customers/${account_id}/cards?status=active - Headers: same as step 1.
- Save From Response:
{"active_cards": "cards"}
${account_id} in step 2’s URL is the field step 1 just collected, the docs are explicit about this: “later steps can use any field collected by earlier steps as input.” The fields collected by step 1 are automatically available to step 2 by name, no input switch needed.Your processor’s response should be an array shaped like:Toggle the collected fields
active_cards does need to be visible so the agent can verbalize the disambiguation question naturally.Attach to your agent
Customer Identity on, leave Chat selected (Forms render reliably in the widget chat surface), and click Save.Verify the chain
active_cards array. Each Data Collection Step also has its own per-step Play button you can use to test in isolation.What can go wrong at this stage
What can go wrong at this stage
- Step 2’s URL renders with literal
${account_id}: step 1 didn’t populateaccount_id. Click the per-step Play on step 1 and inspect the response, theSave From Responsemapping’s right-hand path doesn’t match your API’s actual field name. - The cards array comes back empty for a customer you know has cards: your
status=activefilter is too narrow, or the processor treats frozen / blocked cards as non-active even when the customer regards them as their card. Adjust the URL filter, or expose multiple statuses in the array so the agent can show the customer their frozen card too. - Multiple cards but no
last_four: your processor’s response uses a different key (pan_last_4,last4,mask). Fix the Save From Response mapping; the Read node below depends on the key beinglast_fourexactly.
Step 2: Configure the two Actions
The Attribute pulls customer state in; two Actions push two changes out, in order. Both Actions are workspace-level, you configure them once, and any rule in any agent can invoke them via a Tool node.2a. Freeze Card
Create the action
- Name:
Freeze Card - Description:
Freezes a specific card so it can no longer authorize new transactions. Returns a confirmation id for downstream actions to reference.
Define the inputs
reason is required by most card processors for compliance (the freeze gets recorded with a reason in the card’s audit log). Acceptable values are usually lost, stolen, compromised, customer_request.Define the outputs
frozen_at is an ISO 8601 timestamp. The Reply doesn’t need it directly, but the next Action (Order Replacement Card) records it for the new card’s audit trail.Add the Data Step
- Step Name:
Freeze card via Cards API - Method:
POST - URL:
https://api.yourbank.com/v1/cards/${card_id}/freeze - Headers:
{"x-api-key": "your-cards-api-key"}(or your processor’s native scheme). - Body:
{"reason": "${reason}", "idempotency_key": "${card_id}-freeze"} - Save From Response:
{"freeze_confirmation_id": "id", "frozen_at": "frozen_at"}
Idempotency-Key) rather than the body, check your processor’s docs.Test, then save
card_id from your sandbox processor (with a test card you can safely freeze). Confirm the response returns 200 and both output fields populate.2b. Order Replacement Card
Create the action
- Name:
Order Replacement Card - Description:
Orders a replacement card on the customer's account, shipped to the address provided. Records the prior freeze confirmation for audit. Returns the new card id, expected delivery date, and tracking URL.
Define the inputs
prior_freeze_confirmation_id required is the critical link: it forces the Rulebook tree to bind this input from an upstream Tool’s output, which means the freeze Tool must have run successfully before this Tool can validate. This is how Fini’s type system enforces “don’t ship a card unless the old one is frozen.”Define the outputs
expected_delivery is ISO 8601 again. tracking_url is the carrier’s tracking page (UPS, FedEx, USPS, depending on your processor’s shipping partner). last_four is the new card’s last four digits, surfaced so the customer can match it when the card arrives.Add the Data Step
- Step Name:
Order via Cards API - Method:
POST - URL:
https://api.yourbank.com/v1/customers/${account_id}/cards - Headers:
{"x-api-key": "your-cards-api-key"}(or your processor’s native scheme). - Body:
- Save From Response:
{"new_card_id": "id", "expected_delivery": "shipping.estimated_delivery", "tracking_url": "shipping.tracking_url", "last_four": "last_four"}
Test, then save
account_id and a recent freeze confirmation from your sandbox. Confirm the response returns 200 and all four output fields populate. Inspect the response carefully, shipping fields tend to be nested under different keys depending on the processor; adjust Save From Response accordingly.What can go wrong at this stage
What can go wrong at this stage
- The order test returns 422 with card_already_replaced : most processors block duplicate replacements against the same freeze. Use a fresh sandbox freeze, or wait for the processor’s cooldown.
tracking_urlis null: some processors only populatetracking_urlonce the card actually ships (hours or days later). For the immediate confirmation reply, fall back to theexpected_deliverydate alone; document this in your Reply wording so customers don’t expect a live link the moment they get the message.- Shipping fails with invalid_country: processors typically use ISO 3166-1 alpha-2 codes (
US,GB,IN). The Form node in Step 3 should constrain the country field to these codes, see the Form configuration below.
Step 3: Configure the Rulebook rule
TheCustomer Identity attribute brings customer and card context in; the two Actions push the freeze and the order out; the Rulebook decides when the work happens, which card to act on, and how the conversation flows.
Create a new rule
Card replacement.Write the Description carefully
“Use this rule when the customer reports a lost, stolen, damaged, or otherwise compromised card and wants a replacement. Example queries: ‘I lost my debit card’, ‘my card was stolen’, ‘someone took my wallet, please send a new card’, ‘my card stopped working, can you ship me a new one’, ‘I need a replacement card’. Do not use this rule for activation, PIN reset, or transaction disputes, those have their own rules.”The negative examples at the end (“do not use this rule for…”) matter a lot when other card-related rules exist in the same workspace. The LLM uses the Description as a positive-and-negative signal.
Add the intent Check
Check. Configure:- Name: Intent is card lost or stolen
- Condition group: in the field picker, expand Tag Groups → pick
Type of Issue(the default group) or your custom routing group. OperatorEquals. ValueCard Lost or Stolen.
Card Lost or Stolen, which catches false positives like “my card stopped working at the ATM” (an activation or PIN issue, not a replacement).Add the account state Check
Check. Configure:- Name: Account is active
- Condition group: in the field picker, expand User Attributes → pick
account_status. OperatorEquals. Valueactive.
Add the Read for card and reason
Read. Configure:- Name: Extract card details
- Look at: Interaction History
- Find these details:
- field
card_last_four, typeString, not required - field
replacement_reason, typeString, required, allowed values:lost,stolen,damaged,compromised
- field
- Instructions: “Extract the last four digits of the card the customer is reporting on, if they mentioned a number like ‘4242’ or ‘card ending in 1881’. If they didn’t mention a number, return an empty string, do not guess. Also extract the replacement_reason from these allowed values:
lost,stolen,damaged,compromised. Examples: ‘I lost my wallet’ → lost. ‘someone stole my card at the gas station’ → stolen. ‘my card got cut in half by the ATM’ → damaged. ‘I saw a charge I didn’t make’ → compromised. If unclear, default to compromised.”
card_last_four is optional because customers often say “my card was stolen” without naming which card. When the customer has only one active card, the Tool will use that card’s id directly; when they have multiple, the agent will ask a disambiguation question before the Form renders.Add the Form node
Form. Configure the Title as Shipping address for your replacement card, and add four required String fields (the Form node documents String, Number, Date as supported types, country is a single-line String that you’ll validate via Form Error):- Form Error 1: Postal code doesn’t match country format. Triggered when the entered
shipping_postal_codefails the regex for the selectedshipping_country. The Form Error specifies which field’s validation it represents; the form re-renders showing the error message until the customer corrects it. - Form Error 2: Street address too short. Triggered when
shipping_streetis fewer than 3 characters.
${shipping_street}, ${shipping_city}, ${shipping_postal_code}, ${shipping_country}) available to the Tools downstream.Reply between each to prompt for the next. Less elegant, but it works. The address-change pattern in Rulebook → A worked example: address change with Form is the canonical reference.Resolve card_id and add the Freeze Card Tool
card_id from a customer’s potentially-multi-card account, do the resolution upstream in a Read node and bind the Tool to the Read’s output.Add a second Read between the existing Extract card details Read and the Form:- Name: Resolve card_id
- Look at: Interaction History + User Attributes
- Find these details: field
card_id, typeString, required. - Instructions: “Given the user_attribute
active_cards(an array of cards withidandlast_four) and the tree variablecard_last_four: ifcard_last_fouris non-empty and exactly one card inactive_cardshas a matchinglast_four, return that card’sid. Ifcard_last_fouris empty butactive_cardshas exactly one entry, return that entry’sid. Otherwise, fail (return empty) so the Fallback below can prompt the customer for clarification.”
Fallback whose second child is a Reply:card_last_four populated. This is the documented “try the primary path; fall back if it doesn’t apply” pattern from the Rulebook reference.Now configure the Freeze Tool itself:- Action: select
Freeze Card. - Inputs:
card_id→ bind to thecard_idtree variable from the Resolve Read.reason→ bind toreplacement_reason(from the earlier Read).
freeze_confirmation_id, frozen_at) become tree variables.Add the Order Replacement Card Tool
Tool. Configure:- Action: select
Order Replacement Card. - Inputs:
account_id→ bind toaccount_id(User Attribute).prior_freeze_confirmation_id→ bind tofreeze_confirmation_id(upstream Tool output).replacement_reason→ bind toreplacement_reason.shipping_street,shipping_city,shipping_postal_code,shipping_country→ bind each to the matching Form output.
prior_freeze_confirmation_id binding is the load-bearing link: this Tool cannot fire unless the upstream Freeze Tool succeeded, the dropdown will only show freeze_confirmation_id once the Freeze Tool is in place above this node.Add the Reply node
Reply. Instructions:“Confirm to the customer that their old card has been frozen and a replacement is on the way. Mention the new card’s last four digits (), and include the tracking URL ($) so they can monitor delivery. Reassure them that their old card can no longer be used. Suggest they keep an eye out for any unauthorized transactions on the frozen card and reach out if they see any.”The
${variable} placeholders interpolate from the Order Tool’s outputs. The Reply node passes this as guidance to the agent’s reply-composition pass; the agent writes the final wording in its voice.Assign to your agent
Customer Identity to. A rule with no agents assigned never runs.Test before publishing
form_submission object simulates the Form submission, the test panel injects these values as if the customer had filled the form. Click Run Test and walk the trace. A successful run looks like this:- Both top Checks pass.
- The Read populates
card_last_four = 4242andreplacement_reason = stolen. - The Form node passes (with the injected
form_submissionvalues). - The Freeze Tool fires with
card_id = card_42and produces afreeze_confirmation_id. - The Order Tool fires with that confirmation flowing into
prior_freeze_confirmation_id, plus all four shipping fields from the Form. - The Reply interpolates
new_card_id,expected_delivery, andtracking_urlfrom the Order Tool’s outputs.
prior_freeze_confirmation_id binding being missing, the dropdown only populates once the upstream Freeze Tool is correctly configured.Publish
What can go wrong at this stage
What can go wrong at this stage
- The rule fires on activation messages: your Description’s negative examples weren’t strong enough, or the
Topictag is too broad. Tighten both. Reuse the AI Steps trace’s Planning section to confirm which rule the Planner picked. - The Form node doesn’t render: the deploy channel is email-only. See the Channel notes callout above for the Read-based alternative.
- The Freeze Tool fires but the Order Tool short-circuits: the
prior_freeze_confirmation_idbinding is missing or the upstream Tool failed. Open the trace and look at the Freeze Tool’s output values; iffreeze_confirmation_idis empty, the freeze API itself errored (most often a 404 on thecard_idbecause the card doesn’t belong to that account). - The customer’s address in the Form doesn’t match what the processor accepts: US processors generally require five-digit zip codes; international processors accept various formats. The Form Error children should catch obvious mismatches, but processor-side validation may still reject. Pass the error message back to the customer in a Fallback child.
Step 4: Configure the Reply Behavior fraud gate
You’ve now got a working card-replacement flow. This step adds a safety net: for high-risk customers or customers with repeat replacements in the last 30 days, the agent should still run the full flow (freeze the card, draft the confirmation) but post the confirmation as an internal note instead of replying to the customer directly. A human reviews the case, optionally calls the customer to verify identity, and then either approves the replacement or reverses it before the new card actually ships. This is the canonical fraud-prevention pattern for fintech: don’t refuse to help the customer (refusing makes the agent useless when fraud is most likely), but require human approval before the side effect customers actually feel (the replacement card landing in someone’s mailbox).Open the Internal Comment card
Add the first condition group: high-risk customers
User Attributes → is_high_riskEqualsTrueTag Groups → Type of IssueEqualsCard Lost or Stolen(useContainsinstead if your tag group has Tag Selection set to “Multiple tags can be selected”)
Add the second condition group: repeat replacements
User Attributes → recent_replacement_countGreater Than1Tag Groups → Type of IssueEqualsCard Lost or Stolen
Enable and save
What can go wrong at this stage
What can go wrong at this stage
- The card doesn’t enable: there’s a predicate row with no value. Walk through each row and confirm every field, operator, and value is filled in.
- The Internal Comment fires when it shouldn’t: check whether your
recent_replacement_countfield is being correctly populated by the Customer Identity attribute, a default-of-null behaves like zero in some processors and like unknown in others. If unsure, add a third predicate:recent_replacement_count Is Not NullANDGreater Than 1. - Internal Comment doesn’t fire when it should: a higher-priority No Reply rule may be catching the conversation first. Audit your No Reply rules per Reply Behavior → When multiple cards match. Priority order: No Reply > Internal Comment > Direct Reply.
Verify the flow end-to-end
With everything saved and published, send four test messages from a real conversation surface (your widget, primarily, since the Form needs the chat channel). Use test customer accounts whose accounts resolve through your card-management API and whose cards you can safely freeze.- Standard, single-card, with last_four mentioned. “I lost my card ending in 4242, please send a replacement.” Within seconds, the agent freezes the card, asks for the shipping address via the Form, the customer fills it in, the replacement ships, and the customer sees a direct reply confirming the new card’s last four and expected delivery.
- Standard, multi-card, no last_four. “My wallet was stolen.” The agent disambiguates by asking which card. The customer replies with “my debit card” or “the one ending in 1881”, the rule re-fires with the new context, the Form renders, and the replacement ships.
-
High-risk customer. Same as test 1, but the customer’s
is_high_riskis true. The Rulebook still runs the full tree, the card is frozen, the order is placed, the reply is drafted, but the reply posts as an internal note instead of going to the customer. A teammate reviews and follows up. -
Account closed. Same message, but a customer whose
account_statusisclosed. The account-state Check at the top fails, the rule short-circuits cleanly, and the agent falls through to its default knowledge-base reply. No Tool fires, no card is touched.
Lock it down with a regression test
Card replacement is one of the highest-stakes flows in fintech support; regress this rule and you ship cards to the wrong people. Add it to your Test Suite immediately after publishing. A minimum useful test set:account_status Equals active fails, when the disambiguation Reply runs because the Resolve Read couldn’t pick a card, the agent answers from your Knowledge base. For this walkthrough to feel complete to customers, make sure your Articles cover: “We can’t replace cards for closed or suspended accounts”, “What to do if you have a card that’s frozen but not lost”, and the security guidance you’d want a customer to hear if they’re confused about whether they were compromised. Background AI watches conversations and proposes Articles for gaps it detects, check the Review queue weekly.What to vary
Once the basic flow is in place, common adaptations:- Add Apple/Google Wallet provisioning. Chain a third Tool after the Order Action that calls your processor’s provisioning endpoint to issue a virtual card the customer can use immediately on a digital wallet, useful when the customer needs to keep transacting while waiting for the physical replacement.
- Skip the Form for known-address customers. If
Customer Identityexposes a verified billing address, gate the Form behind a Check:billing_address_verified Equals False. When the address is already verified, jump straight from Read to the Tools using the attribute’s address fields. - Expedited shipping for VIPs. Add a Check on
is_vipbetween the Form and the Order Tool. Ifis_vip Equals True, set the Order Tool’s body to include"shipping_speed": "overnight". The processor charges your account the upcharge automatically. - Two-card pre-emptive freeze. If the customer says “my wallet was stolen” and has multiple cards, you may want to freeze all active cards (not just the one they identified). The Rulebook doesn’t have a generic iteration node, so the cleanest pattern is to extend your
Freeze CardAction server-side to accept either a singlecard_idor anaccount_id(freezing all cards on that account), and bind the Tool toaccount_idwhen the customer’s wording is “wallet” / “all my cards” / etc., gated by a Check on afreeze_allboolean populated by an upstream Read. - Suspicious activity sweep. After ordering the replacement, chain a
Lookup Recent TransactionsAction that pulls the last 24 hours of activity on the frozen card and includes any flagged transactions in the Reply, so the customer can immediately flag any they didn’t authorize. - Localized shipping countries. Pre-populate the Form’s
shipping_countrydropdown with the customer’s known country first (fromCustomer Identity), then a sorted full list. Reduces form friction for international customers.
Troubleshooting
Stages where this walkthrough commonly gets stuck, ordered by likelihood.The rule fires on the wrong intent (activation, PIN reset)
The rule fires on the wrong intent (activation, PIN reset)
The Read populates the wrong card_last_four
The Read populates the wrong card_last_four
The Form looks broken on a specific deploy channel
The Form looks broken on a specific deploy channel
The Order Tool runs but ships to the wrong address
The Order Tool runs but ships to the wrong address
A duplicate freeze creates two freeze records in the audit log
A duplicate freeze creates two freeze records in the audit log
Idempotency-Key) instead, (3) the key is deterministic across retries, ${card_id}-freeze is deterministic; ${now} is not.The Internal Comment fires for VIPs but not high-risk customers
The Internal Comment fires for VIPs but not high-risk customers
is_high_risk attribute isn’t populating, or the predicate has a value mismatch (true vs True, type coercion). Open the conversation in the Inbox, expand AI Steps → Executed User Attributes, and click the Customer Identity row to see the actual returned values. Compare against the predicate.A replacement was ordered to a wrong address, can it be reversed?
A replacement was ordered to a wrong address, can it be reversed?
The Order Tool succeeds but the Reply shows null for tracking_url
The Order Tool succeeds but the Reply shows null for tracking_url
Related
Cancellation flow
Order status and changes
Rulebook
Actions
Attributes
Reply Behavior
Test Suite
Tags
Topic tag group the intent Check references.
