What the agent does
The agent starts with Stripe's own record. Through the Stripe connection named on the run, it lists the failed webhook deliveries for the endpoint over the last 7 days and groups them by event type, so the report opens from what actually failed rather than from a guess about the handler. In test mode it can also read the event payloads Stripe tried to deliver.
Then it reproduces each failing event type against the code. In a repository like nextjs/saas-starter the webhook route is app/api/stripe/webhook/route.ts: it reads the raw body with request.text(), pulls the stripe-signature header, verifies with stripe.webhooks.constructEvent using STRIPE_WEBHOOK_SECRET, answers 400 on a bad signature, and switches on only customer.subscription.updated and customer.subscription.deleted, passing each to handleSubscriptionChange in lib/payments/stripe.ts. The agent installs dependencies with pnpm, sets the env values, starts the app, and runs the Stripe CLI in the sandbox: stripe listen --forward-to localhost:3000/api/stripe/webhook as the README describes, plus stripe trigger customer.subscription.updated to replay the exact event shapes that are failing.
What it finds in this codebase is the kind of failure that never shows up as a 500. handleSubscriptionChange looks the team up by Stripe customer id; when no team matches it logs and still returns 200 received true, so Stripe marks the delivery successful while the subscription state never lands in the database, and the dashboard fills with silent drift. Another gap hides in plain sight: the product on the subscription's plan is cast to a Product without expand, so planName can end up read from a string id. And when the signature secret is missing or stale, constructEvent throws on every delivery and the endpoint answers 400 for every event type at once, a pattern the delivery list makes obvious.
The repository has no tests and no CI, so the reproduction is the evidence. The agent writes a report that lists each failing event type, what the handler did with it in the sandbox (the forwarded payload, the response code, the database row before and after), the line that explains the behaviour, and the fix to make, ranked. The prompt says to change no code, and it doesn't.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Through the Stripe connection, list the failed webhook deliveries for this account's endpoint in the last 7 days. Reproduce each failing event type against the app's handler with the Stripe CLI in the sandbox, find why it fails, and write a report. Change no code.",
"resources": [
{
"url": "https://github.com/your-org/your-app"
}
]
}- Get an API key. In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
- Send the request. POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
- Read what came back. Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.
What comes back
What comes back is a report, the run's artifact. It opens with the delivery list straight from Stripe: event types, failure codes, and when they started. One section per failing event type follows, each with the sandbox reproduction: the forwarded payload, the response the handler returned, and what changed or failed to change in the database.
The causes are separated from the symptoms. A 400 on every delivery traces to signature verification and the STRIPE_WEBHOOK_SECRET env value; a 200 that changes nothing traces to the team lookup in handleSubscriptionChange; a stored planName that looks like an id traces to the unexpanded product cast. The trajectory keeps every Stripe CLI command with its output and every handler read, so each claim replays.
Before acting, the reviewer checks that each reproduction matches a real failed delivery in the list, and that the suggested fix for each cause is scoped to the handler rather than a rewrite of the payments flow. After the fix ships, the same request run again against the endpoint tells you the failure pattern is actually gone, because Stripe's delivery list is the ground truth.
Run it on every webhook failure reported
Stripe already tells you when an endpoint fails: configure a webhook endpoint notification, or poll the delivery list on a schedule, and have your server post this same request when failures appear for the endpoint. One request starts one run, so a burst of failures after a deploy gets investigated once, with the event types in the prompt windowed to the last 7 days.
Set a webhook URL on the API key and Rush posts run.completed or run.failed when the run's turn finishes, carrying the session URL; your server can attach the report to the Stripe alert or the payments channel. Name the Stripe connection on every run, since a key run reads only the connections it names.
The use case this job follows, with its automation and trigger: Investigate an incident.
Why run it on Rush?
Debugging webhooks on a laptop means tunneling a public URL to localhost, keeping stripe listen running, and guessing which secret and which env your local app actually loaded. On Rush the agent runs the app in its own sandbox, forwards the Stripe CLI to it, and replays real event shapes, with every command and response kept in the trajectory.
The report is grounded in Stripe's delivery list, not in what the handler logs happened to print, which matters for the failure mode that answers 200 while doing nothing. Because the reproduction happens against the real route and a real Postgres database in the sandbox, the before-and-after rows in the report are checkable evidence, and a follow-up run can implement the ranked fixes on the same evidence.
Frequently asked questions
How does the agent list my failed deliveries?
You connect Stripe once from the console and name the connection on the run; a key run reads only the connections it names. The agent reads the endpoint's delivery attempts, failure codes and event payloads through it, in test or live mode depending on the account you connected.
Stripe says 200 OK but my database doesn't update. Can it find that?
Yes, that is the silent-drift failure this page is built around: the handler returns received true while the team lookup fails or the update is skipped. The agent catches it by comparing the database row before and after each replayed delivery, not just the HTTP status, and the report shows the rows as evidence.
Will it fix my webhook handler?
Not in this run; the prompt says to change no code, and the deliverable is the report with ranked causes and the line for each. A follow-up run with a described fix implements it in the same repository and opens a pull request.
Does the reproduction need my production database?
No. The sandbox sets up a fresh database with the app's own migrations and seed, replays the event shapes Stripe recorded, and shows whether the handler writes what it should. Only the event payloads and failure list come from your Stripe account.
My app uses the Pages Router or a different webhook path. Does it still apply?
Yes. The investigation follows wherever your route lives; nextjs/saas-starter keeps it at app/api/stripe/webhook/route.ts, your app may differ, and the agent reads the actual path from the repository and your Stripe dashboard configuration before replaying anything.