Automate Your Stripe Risk Response: The Complete Workflows Tutorial

MP
Mikita Pitunou
FOUNDER & CEO
Published
MAY 8, 2026

Alerts tell you something is happening. Workflows decide what to do about it. This is the tutorial we wish existed when we first shipped Workflows last month — a full walkthrough of the model behind them, every building block you can use, and five copy-paste recipes that cover most of what we see customers automate in their first week.

What Workflows Actually Are

A FreezeAlert Workflow is a small, deterministic program that runs every time something notable happens in your Stripe account. It is built from three pieces: a trigger (the event that wakes the workflow up), zero or more conditions (filters that decide whether the event is worth acting on), and one or more actions (what to do when it is).

Workflows live next to your alerts but they are a different tool. An alert ends with a notification — someone has to read it and decide. A workflow ends with a side effect: a Slack message in a specific channel, a paused payout, a refund pushed back to the customer, a row written to your data warehouse. The point is to take the boring, predictable decisions you would have made manually and let them happen at 3 AM without you.

A few things that are deliberately true about how they run:

  • Each workflow runs in its own isolated execution. A failing action does not block other workflows.
  • Every run is logged with input, output, and the exact branch that executed. You can replay any run.
  • Workflows are append-only on your Stripe account: we never write anything to Stripe unless an action explicitly says so, and those actions require a separate scope grant.

The Three Building Blocks

Triggers

A trigger is the event that starts a workflow. We currently expose ten of them, grouped into four families:

  • Risk scorerisk_score.crossed_threshold, risk_score.changed_band (green→yellow→red).
  • Disputesdispute.created, dispute.lost, dispute_rate.crossed_threshold.
  • Volumevolume.anomaly_detected, volume.spike, volume.drop.
  • Account eventsstripe.review_opened, stripe.payout_paused, stripe.kyc_requested.

Every trigger fires with a payload — a structured JSON object describing what happened. You can read fields out of the payload in conditions and actions using the {{ trigger.* }} syntax, which we will use throughout the recipes below.

Conditions

Conditions are optional filters between the trigger and the actions. Each condition is a single boolean expression evaluated against the trigger payload, your account state, or the result of a previous step. Multiple conditions are combined with explicit AND / OR groups — there is no implicit precedence, so the editor always renders the tree.

Common things to filter on:

  • Amount or rate. trigger.dispute.amount > 500, trigger.dispute_rate > 0.0075.
  • Customer signal. trigger.customer.lifetime_value > 1000, trigger.customer.country in ['US','CA'].
  • Time of day. now.hour between 9 and 18 to suppress automated actions outside business hours.
  • State. account.risk_band == 'red' so a workflow only escalates when you are already in danger.

If no condition is set, every trigger event runs every action. That is fine for notifications and almost never fine for anything that touches money.

Actions

Actions are the side effects. The current set:

  • Notify — Slack channel, email distribution list, SMS, PagerDuty, generic webhook.
  • Stripe — refund a charge, capture or release an authorization, mark a charge as fraudulent, attach a customer note. Each of these requires an explicit write scope.
  • Internal — write to your FreezeAlert audit log, add a tag to a customer, create a case for your ops team to review.
  • Control flow — wait N minutes, branch on a condition, run another workflow.

Every action returns a result that the next step can read. So a workflow can refund a charge, take the refund ID, and post it into Slack with a deep link — all without leaving the editor.

Tutorial: Build Your First Workflow

We are going to build the workflow we recommend everyone start with: “Page me if my dispute rate crosses 0.7%.” It is the closest analog to the alerting most people already have, and it is the cleanest way to learn the editor without risking a write to Stripe.

Step 1 — Open the editor

In your dashboard, click Workflows → New Workflow. Name it dispute-rate-page. Names are used as Slack message titles and audit log keys, so prefer kebab-case.

Step 2 — Pick a trigger

Click the empty trigger node and choose dispute_rate.crossed_threshold. The editor will ask for a threshold value: type 0.007 (0.7%). The trigger now fires whenever your 14-day rolling dispute rate first crosses that line — not on every transaction after, just on the crossing.

Step 3 — Add a condition

Click the + between trigger and action and choose Condition → New rule. We want to ignore the threshold crossing if it was caused by a single large dispute that has already been won. Add:

trigger.window.disputes_count >= 3
AND account.risk_band != 'green'

The first clause stops single-dispute false alarms. The second skips the page if your overall risk is fine — sometimes the rate spikes briefly on low volume.

Step 4 — Add the notify action

Click + again and choose Notify → Slack. Pick the channel (we recommend a dedicated #stripe-risk channel rather than #general), and paste this template into the message body:

:rotating_light: Dispute rate crossed {{ trigger.threshold | percent }}.
Current: {{ trigger.current | percent }} over {{ trigger.window.days }} days
({{ trigger.window.disputes_count }} disputes / {{ trigger.window.charges_count }} charges).
Risk band: {{ account.risk_band }}. Open dashboard → {{ account.dashboard_url }}

The | percent filter formats 0.0073 as 0.73%. There are a handful of these filters — currency, relative_time, truncate(n) — documented in the editor sidebar.

Step 5 — Test it

Hit Test run in the top right. The editor lets you pick a real historical event from your account or a synthetic payload. Pick a synthetic one for now: it will execute the workflow end-to-end but mark every action as [TEST] so your team knows it is a drill. Watch the run log update in real time. If the Slack message arrived, you are done.

Step 6 — Activate

Toggle the workflow to Active. From this moment on, every real dispute_rate.crossed_threshold event runs through it. If you ever want to pause without losing the configuration, toggle it back to Draft — paused workflows do not lose their run history.

That is the whole loop: trigger, condition, action, test, activate. Everything else is combinations of these four moves.

Five Recipes Worth Stealing

These are the workflows we see in the most accounts, in roughly the order people build them. Each one uses only blocks we covered above.

1. Auto-refund tiny disputes from repeat customers

A $9 dispute from a customer with $4,000 in lifetime spend is almost never worth fighting. Refund it, save the chargeback fee, and move on.

  • Trigger: dispute.created
  • Condition: trigger.dispute.amount < 25 AND trigger.customer.lifetime_value > 500
  • Actions: Stripe → refund charge, then Notify → Slack with the refund ID and customer name.

2. Pause new high-risk charges when Stripe opens a review

If Stripe is already looking at your account, you do not want to feed them more questionable volume in the next hour.

  • Trigger: stripe.review_opened
  • Condition: (none — every review matters)
  • Actions: Stripe → enable strict Radar rule, Notify → PagerDuty, Wait 60 minutes, Run workflow → review-status-check.

3. Daily risk digest at 9 AM

Less an automation, more a forcing function. Use the scheduled trigger to push a summary into Slack every weekday morning so the whole team starts with the same picture.

  • Trigger: schedule.cron('0 9 * * 1-5')
  • Actions: Notify → Slack with {{ account.daily_digest }} (a built-in template that renders score, rate, and top 3 risks).

4. KYC request → escalate to a human

When Stripe asks for documents, you have hours, not days. Make sure it never sits in an inbox.

  • Trigger: stripe.kyc_requested
  • Actions: Notify → Email to your ops alias, Notify → SMS to the on-call founder, Internal → create case with priority P1.

5. Volume spike → confirm or unwind

A sudden 5x volume spike is either the best day of your year or fraud. Treat it as suspicious by default and let a human override.

  • Trigger: volume.spike
  • Condition: trigger.spike.multiplier > 5
  • Actions: Notify → Slack with an Approve and Roll back button (interactive Slack actions). On Roll back, run a sub-workflow that flags new charges as fraudulent for the next two hours.

Testing, Versioning, and Debugging

A workflow that looked right in the editor can still misfire on a real payload. A few habits will save you:

  • Always Test run before activating. It is one click. Skip it once and you will refund $4,000 to a chargeback ring at 2 AM.
  • Stage write actions behind a manual approval at first. Replace Stripe → refund charge with Notify → Slack: should we refund? for the first week, then promote it once you trust the trigger and condition combination.
  • Read the run log. Every run shows the exact payload, the result of each condition, and the output of every action. If a workflow fired when you did not expect it, the log shows you which condition evaluated to true and why.
  • Pin a version before editing. Workflows are versioned automatically. Use Save as draft to edit without affecting the active version, then promote with one click. Every promotion is reversible from the run log.

Best Practices

A few rules of thumb from watching hundreds of accounts use this:

  • One workflow, one job. Resist the urge to put the daily digest, the dispute auto-refund, and the KYC escalation into one giant tree. Smaller workflows are easier to test, version, and disable independently.
  • Notify before you write. For the first month, lean on Notify → Slack with a deep link instead of Stripe → refund. Once you have logged 50 cases that you would have approved manually, promote the workflow to do it automatically.
  • Use distinct Slack channels per severity. A #stripe-fyi for digests, a #stripe-risk for thresholds, a #stripe-page for KYC and reviews. Mixing severities trains people to ignore the channel.
  • Review monthly. Once a month, open the run log and look for workflows that fired but produced no value. Either tighten the condition or delete the workflow. Dead workflows are noise.

Get Started

Workflows are available on every paid FreezeAlert plan today, with a fair-use limit on monthly runs that is high enough that no current customer has hit it. Open Dashboard → Workflows → Templates and you will find every recipe in this post preloaded — clone any of them, adjust the channel and thresholds, and you are running in under a minute.

If you build something clever, send it to us. The library of public templates is curated from customer submissions, and the next person setting up FreezeAlert will thank you.


Found this useful?

Subscribe to get engineering deep dives every week.