Skip to main content

End-to-end: card-fraud triage

Advanced ⏱ 25 min banking · end-to-end · all four engines

You will build: the complete fraud pipeline from the Banking use case: per-card velocity in a sliding window, deterministic triage in microseconds, an LLM ruling only on the gray zone, and a webhook to your case system. This is the marketing page's promise, made runnable.

auth events → streaming:velocity → rule-based:triage → llm:judge → webhook (case system)

Prerequisites

  • The quickstart completed (local Pulse running).
  • An LLM provider for step 4 — ANTHROPIC_API_KEY or local Ollama. Without one, stop after step 3: the deterministic half of the pipeline is already a working detector.
  • A webhook URL to receive cases (a webhook.site URL works for the exercise).

1 · Scaffold the four stages

pulse new fraud --source webhook \
--stage streaming:velocity --stage rule-based:triage --stage llm:judge \
--sink webhook

2 · The velocity stage — per-card features in a sliding window

stages:
- name: velocity
engine: streaming
operators:
- { type: keyBy, field: card_id }
- type: window
spec: "sliding(5m,1m)"
aggregations:
tx_count: "count()"
spend: "sum(amount)"
hi_amount: "countWhere(amount > 500)"

Every card gets its own window; every aggregation is validated at deploy.

3 · The triage stage — the obvious cases never cost a token

- name: triage
engine: rule-based
rules:
- { condition: "tx_count > 20 || spend > 10000", action: flag }

Only windows that break the velocity thresholds pass through — the LLM will never see normal traffic.

4 · The judge stage — reasoning over the residue

- name: judge
engine: llm
systemPrompt: |
You receive per-card velocity features that crossed
fraud thresholds. Assess likelihood (low|medium|high),
justify in one sentence, and recommend: allow, review, or block.
temperature: 0.2

5 · The sink — cases out

pulse secret set CASE_WEBHOOK https://webhook.site/<your-id>
sink:
kind: webhook
url: ${secret:CASE_WEBHOOK}

6 · Deploy and simulate a card gone hot

pulse secret set PULSE_WEBHOOK_SECRET dev-secret # the scaffolded webhook source requires it
pulse deploy .
for i in $(seq 1 25); do
pulse events publish --topic fraud.in --value '{"card_id":"c-77","amount":'"$((RANDOM % 900 + 100))"'}'
done

7 · Watch each altitude of the decision

pulse events tail --topic fraud.velocity.out # features accumulating per card
pulse events tail --topic fraud.triage.out # the moment thresholds break
pulse events tail --topic fraud.judge.out # the model's ruling on the flagged window

And check your webhook receiver: the case arrived with the velocity features and the LLM's recommendation attached.

8 · Prove the isolation

pulse events publish --topic fraud.in --value '{"card_id":"c-01","amount":25}'

c-01 builds its own quiet window: never flagged, never judged, never a token spent. That asymmetry — µs rules for the many, model judgment for the few — is the economics of the whole design.

What just happened

You composed all four decision altitudes into one declarative file and watched an event stream sort itself: raw auths → per-card features → deterministic flags → reasoned rulings → an acted-upon case. Every intermediate step is a durable topic you tailed — which is also your audit story: the pipeline's reasoning is inspectable at every seam.

Troubleshooting

Nothing reaches triage

The window has to actually break a threshold: 25 events in under 5 minutes on one card_id does it. Check fraud.velocity.out first — if features flow but no flag, your thresholds are higher than the simulated burst.

judge is unhealthy

No LLM provider configured. Export ANTHROPIC_API_KEY (or point at Ollama) and restart — or stop at step 3 and ship the deterministic detector; it's already useful.

Next