Based on: A worked playbook on Stripe's charge.dispute.created event and the dispute evidence API.
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
- 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. - The machine.
auth: stripeverifiesStripe-Signature(t=…,v1=…, an HMAC-SHA256 over the timestamp and the body, within five minutes);whenkeeps the event type;dedupeon the event'sid, because Stripe sends no delivery header. - The job. It reads the dispute, the charge, the customer and the invoices with a restricted key; stops with
nothing_to_dowhen the dispute was already answered; gathers the evidence for the dispute's reason, uploads files withpurpose=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. - 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_humanalert 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.
submit=true it cannot be changed. The skill checks evidence_details.submission_count before it does anything, so a replay or a retry never submits twice, and "Hold" saves the evidence without submitting so a person can finish it in the Dashboard.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_humanand 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.createdas an accepted delivery with its job; a rejected one withinvalid_signaturemeans the endpoint's secret in.envis 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
sourcelink, where the status readsunder_reviewafter a submission. - Replay once a cause is fixed (the key's permissions, a connection string):
replay_deliveryruns 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
livemodefilter in the frontmatter so test events no longer start runs.