CustomersA worked playbook

How to answer Stripe disputes with the evidence already gathered

A dispute arrives with a deadline. The skill collects the customer, the invoices, the access and usage logs, the correspondence and the delivery records, writes the evidence up, submits it through the API when it is complete, and asks a person first when it is thin or the amount is large.

Based on: A worked playbook on Stripe's charge.dispute.created event and the dispute evidence API.

Trigger
Stripe's charge.dispute.created event
Submits alone when
The evidence is complete and the amount small
Asks a person when
The evidence is thin or the amount is large
Hosted URLInboxAlertsReplay

The situation

A dispute is a deadline with a form attached. Stripe wants the evidence in specific fields (the customer, the product, the receipt, the access log, the correspondence, the delivery records), and a dispute nobody answers is lost by default. Most of that evidence is in systems a machine can read: Stripe itself, the application's database and logs, the support mailbox. This playbook gathers it the moment the dispute is created, writes it up the way the API takes it, and either submits or asks.

How it runs

  1. Stripe. A webhook endpoint for charge.dispute.created, pointed at the skill's hosted URL, created in test mode first. Its signing secret (whsec_…, shown once) goes into the machine's .env.
  2. The machine. auth: stripe verifies Stripe-Signature (t=…,v1=…, an HMAC-SHA256 over the timestamp and the body, within five minutes); when keeps the event type; dedupe on the event's id, because Stripe sends no delivery header.
  3. The job. It reads the dispute, the charge, the customer and the invoices with a restricted key; stops with nothing_to_do when the dispute was already answered; gathers the evidence for the dispute's reason, uploads files with purpose=dispute_evidence, and writes an evidence table with the source of every field. Then it decides: submit through the API when the evidence is complete and the amount is at most the configured maximum, otherwise ask.
  4. The inbox. The job under "Dispute <id>: <reason>, <amount>", the evidence summary and the gaps in the question's context, three buttons, the due date in the headline; the needs_human alert goes out.

Set it up with your agent

Paste this into Claude Code or Codex with the skillhook plugin. It installs skillhook and the Stripe CLI, has you log the CLI in and store a restricted key in your own terminal, finds the read-only sources the machine has, creates the skill, turns on the hosted URL, registers the endpoint and triggers a test dispute, all in test mode. Paste the SKILL.md from the next section as your second message.

The skill

The frontmatter is the stripe preset with the event filter and the event-id deduplication skillhook's documentation recommends for Stripe. The body maps what the machine can read to the fields of Stripe's dispute evidence object, states the four conditions for submitting alone, and gives the one question it may ask. Every frontmatter field is one skillhook validates.

Where a person comes in

  • The threshold. DISPUTE_AUTO_SUBMIT_MAX, in the currency's minor units, is the largest amount the skill may submit alone. Above it, it always asks.
  • Thin evidence. A reason the records do not answer, a customer whose identity does not match the charge, no access log, delivery record or correspondence, or an open refund conversation with the team: it asks.
  • The question. "Dispute <id> (<amount>, <reason>): submit this evidence?" with Submit as drafted, Hold: I will add evidence and Accept the dispute. It waits an hour with the job's timeout clock paused; a dispute has days, so without an answer the run ends with needs_human and the due date in its headline, and the pick in the inbox resumes it.
  • What it never does. Accept a dispute (that ends it in the customer's favour and is a person's decision in the Dashboard), refund, or change the customer, the subscription or the charge.

What to check after

  • Deliveries. stripe trigger charge.dispute.created as an accepted delivery with its job; a rejected one with invalid_signature means the endpoint's secret in .env is another endpoint's (test and live endpoints have different secrets).
  • The job. The evidence table and the gaps in its summary, the deadline, the dispute in the Dashboard as its source link, where the status reads under_review after a submission.
  • Replay once a cause is fixed (the key's permissions, a connection string): replay_delivery runs the event again, and the skill's first step finds the submission count and stops when the dispute was already answered.
  • Going live. A live restricted key, a live endpoint with its own secret, and the livemode filter in the frontmatter so test events no longer start runs.

Alerts · Questions for a person · Hosted webhook URLs