How to Audit Stripe Disputes with AI and Escalate in Slack
Build a Spojit workflow that runs every morning, pulls your recent Stripe charges, uses an AI agent to score each one for dispute and chargeback risk, and posts the flagged cases to a Slack channel so your finance team can act before the money is gone.
What This Integration Does
Disputes and chargebacks usually surface days after the risky charge was processed, by which point the funds are already withdrawn and the evidence window is shrinking. This workflow flips that timing around. Each day it reviews the charges that just landed, asks an AI agent to judge which ones look risky (unusual amounts, mismatched customer history, refund-prone patterns, or charges that already carry dispute or refund signals), and surfaces only the suspicious ones in Slack. Your team reviews a short, ranked list instead of scrolling the full Stripe dashboard.
A Schedule trigger starts the run on a fixed cron expression in your timezone, so no person has to remember to check. The workflow reads charges through the Stripe connector, hands them to a Connector node in Agent mode where Miraxa, the intelligent layer across your automation, assesses risk and returns structured JSON, then a Condition node decides whether anything is worth escalating. If flagged charges exist, a final Slack step posts them; if not, the run ends quietly. Each run is independent and stateless: it re-reads charges from Stripe every time, so re-running it is safe and simply re-evaluates the current window.
Prerequisites
- A Stripe connection in Spojit (Connections > Add connection > Stripe), using an API key with read access to charges and customers.
- A Slack connection authorized to post to the channel where you want escalations, with permission to send messages.
- The Slack channel ID (or name) for your finance or risk channel, for example
#payments-risk. - Enough AI credits in your workspace for one Agent-mode evaluation per run (Agent mode costs credits; Direct mode and the other nodes do not).
- A decision on your audit window and timezone, for example "the last 24 hours, evaluated at 9am Australia/Sydney".
Step 1: Start the run on a daily Schedule trigger
Create a new workflow and set its trigger to Schedule. Add a schedule with a 5-field Unix cron expression and an IANA timezone. To run at 9am every weekday, use:
0 9 * * 1-5
Australia/Sydney
A single Schedule trigger can hold multiple schedules if you want a second daily sweep. The trigger output is {{ scheduledAt }}, which you can reference downstream (for example in the Slack message header) to stamp when the audit ran.
Step 2: List recent charges with the Stripe connector
Add a Connector node in Direct mode, choose the Stripe connector, and select the list-charges tool. This call is deterministic and costs no AI credits, so keep it in Direct mode. Set limit to the number of charges to pull per run (max 100, for example 100). Leave customer empty to scan all customers. If your volume exceeds the limit, use starting_after with the last charge ID to page through additional batches.
Store the result in an output variable such as charges. Downstream you reference the returned list (each item carries fields like the charge id, amount, currency, status, and customer) when you build the prompt for the agent.
Step 3: Assess dispute risk with a Connector node in Agent mode
Add a second Connector node, this time in Agent mode, so the AI agent can reason over the charge list. In the prompt, describe what "risky" means for your business and pass the charges in with a variable:
You are auditing Stripe charges for dispute and chargeback risk.
Review these charges: {{ charges }}
For each charge, weigh: unusually high amount, mismatched or new
customer, status that is not a clean successful capture, signals of
prior refunds or disputes, and currency or geography that is unusual
for this account. Assign a riskScore from 0 to 100 and a short reason.
Only include charges with a riskScore of 60 or higher.
Turn on a Response Schema so the agent returns predictable JSON instead of prose. A schema like the following keeps the downstream Condition and Slack steps simple:
{
"type": "object",
"properties": {
"flagged": {
"type": "array",
"items": {
"type": "object",
"properties": {
"chargeId": { "type": "string" },
"amount": { "type": "number" },
"currency": { "type": "string" },
"riskScore": { "type": "number" },
"reason": { "type": "string" }
},
"required": ["chargeId", "riskScore", "reason"]
}
},
"flaggedCount": { "type": "number" }
},
"required": ["flagged", "flaggedCount"]
}
Save the result to an output variable such as audit. You can now reference {{ audit.flagged }} and {{ audit.flaggedCount }} in later steps.
Step 4: Branch on whether anything was flagged
Add a Condition node so quiet days do not post empty noise to Slack. Set the condition to check that the agent found something, for example {{ audit.flaggedCount }} is greater than 0. Connect the true output to the Slack step in the next step. Leave the false output unconnected (or route it to a no-op) so the run simply ends when there is nothing to escalate.
Step 5: Format the escalation message
Add a Transform node on the true branch to turn {{ audit.flagged }} into a readable Slack message. Build a single text string that lists each flagged charge with its score and reason, for example one line per charge: charge id, amount and currency, risk score, and the agent's reason. Save the formatted text to a variable such as slackBody. Keeping formatting in a Transform node makes the Slack step a clean single field and lets you tweak wording without touching the connector call.
Step 6: Escalate to Slack
Add a Connector node in Direct mode, choose the Slack connector, and select the send-message tool. Set the target channel and the message body:
channel: #payments-risk
text: Dispute risk audit for {{ scheduledAt }}
{{ audit.flaggedCount }} charge(s) flagged:
{{ slackBody }}
If you prefer the channel ID over a name, use lookup-user-by-email only for direct messages; for a channel, paste the channel ID into the channel field. When the workflow saves and runs, flagged charges land directly in the channel where your team is already watching.
Step 7: Add a Send Email fallback (optional)
For high-risk days you may want a record outside Slack. Add a Send Email node after the Slack step (still on the true branch) to email the same summary to your finance lead. Set Recipients, a Subject such as Stripe dispute audit: {{ audit.flaggedCount }} flagged, and put {{ slackBody }} in the body. The built-in mail service needs no connection, but external recipients must be on your org allowlist (Settings > General > Email recipients), and sends count toward your monthly email allowance.
Tips
- Keep the heavy lifting cheap: only the Agent-mode node spends AI credits. Pulling charges and posting to Slack stay in Direct mode so each run costs one evaluation, not several.
- Tune the risk threshold in both the prompt and the Condition. Start strict (score 70+) to avoid alert fatigue, then lower it once the team trusts the output.
- Use the
scheduledAtvalue in your Slack header so every post is clearly timestamped and you can tell at a glance which audit window it covers. - If you run multiple times a day, give each schedule a window that does not overlap with the previous
limit, or page withstarting_afterso charges are not double-reviewed.
Common Pitfalls
- Timezone drift: the cron expression is evaluated in the IANA timezone you set, not your browser's. Confirm
Australia/Sydney(or your zone) so 9am means 9am locally. - Pagination blind spots:
list-chargescaps at 100 per call. On busy accounts a single call can miss charges, so page withstarting_afteror shorten the window. - Unstructured agent output: without a Response Schema the agent may return prose, which breaks the Condition and Transform steps. Always enable the schema so
{{ audit.flagged }}and{{ audit.flaggedCount }}exist. - Slack permissions: if
send-messagefails, the connection usually lacks access to that channel. For private channels the connection must be invited to the channel first.
Testing
Before turning the schedule on, validate the chain on a small scope. Temporarily set limit to a low number like 5 on the Stripe step and run the workflow with the Run button instead of waiting for the schedule. Inspect the execution log to confirm charges populated, the Agent-mode node returned valid JSON matching your Response Schema, and the Condition routed correctly. Lower the risk threshold in the prompt so at least one charge flags, verify the Slack message posts with readable formatting, then raise the threshold and restore the real limit before enabling the daily schedule. If you get stuck, ask Miraxa "Why did my last run fail?" from any page to investigate the failed run in context.