What the agent does
The agent starts by reading how the app is put together. In an Express API like gothinkster/node-express-realworld-example-app it opens package.json and finds Express ~4.18.1, @prisma/client and prisma at ^4.16.1, TypeScript ~5.2.2, Jest 29 and an Nx 17.2.6 workspace whose start, build and test scripts call nx serve, nx build and nx test. The lockfile is package-lock.json, so it installs with npm ci. It reads src/main.ts, where cors(), bodyParser.json() and bodyParser.urlencoded() are mounted on the whole app before routes, and src/app/routes/routes.ts, which mounts the tag, article, profile and auth controllers under /api.
That order matters for Stripe. stripe.webhooks.constructEvent needs the exact bytes Stripe sent, and bodyParser.json() has already parsed them by the time any router runs. So the agent registers the webhook route in src/main.ts above the JSON parser, with express.raw({ type: 'application/json' }) on that one path, and leaves every other route as it was. The Checkout endpoint goes in a new src/app/routes/checkout/checkout.controller.ts beside the existing controllers, mounted through routes.ts so it lives at /api/checkout. It is protected with auth.required from src/app/routes/auth/auth.ts, the same express-jwt middleware the article routes use, so the Session is created for a signed-in user and carries that user's id as client_reference_id.
Recording the purchase needs somewhere to put it. src/prisma/schema.prisma has User, Article, Comment and Tag on a Postgres datasource and nothing about payments, so the agent adds a Purchase model keyed by the Checkout Session id (unique, so a webhook Stripe retries cannot record the same payment twice) with the user relation, amount, currency and status, and generates a migration next to the four under src/prisma/migrations/. It adds stripe to dependencies, reads STRIPE_SECRET_KEY, STRIPE_PRICE_ID and STRIPE_WEBHOOK_SECRET from the environment the way auth.ts reads JWT_SECRET, and adds a plain success route that confirms the order by reading the Session back from Stripe. The existing service tests in src/tests/services/ mock Prisma through jest-mock-extended in src/tests/prisma-mock.ts, so the agent writes the new service test the same way.
Then it runs the real thing. It starts Postgres in the sandbox, applies the migrations, runs npm run start, and in another shell runs stripe listen --forward-to localhost:3000/api/checkout/webhook with the Stripe test key, which prints the signing secret the server needs. It calls the Checkout endpoint with a JWT for a seeded user, runs stripe trigger checkout.session.completed, and checks that the handler answered 200 and a Purchase row exists. It also sends one request with a tampered signature and confirms the handler answers 400 and writes nothing. Finally it runs npm test and nx build so the pull request shows the existing suite still passes.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Add Stripe Checkout to this Express app for one fixed-price product. Add POST /api/checkout, behind the existing auth.required middleware, that creates a Checkout Session for STRIPE_PRICE_ID with the user's id as client_reference_id. Add a success route that reads the Session back from Stripe. Add POST /api/checkout/webhook, registered in src/main.ts before bodyParser.json() with express.raw so the signature check sees the raw body, that verifies the Stripe-Signature header with STRIPE_WEBHOOK_SECRET and records checkout.session.completed in a new Prisma Purchase model keyed by the Session id, with a migration. Test it in the sandbox: run Postgres, start the server, forward events with stripe listen, run stripe trigger checkout.session.completed, and also send one request with a bad signature. Run npm test and nx build. Open a pull request whose description includes the stripe listen output, the trigger output and the test output.",
"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
A pull request on a branch of its own. The diff touches src/main.ts (the raw-body webhook route above the JSON parser), src/app/routes/routes.ts (one more .use), a new src/app/routes/checkout/ folder with the controller and a service, src/prisma/schema.prisma with the Purchase model, one new migration folder, a test under src/tests/services/, and package.json with package-lock.json for the stripe dependency. The description lists the three environment variables the code reads and pastes the terminal output from the sandbox: stripe listen forwarding the event, the handler's 200, the 400 for the bad signature, and the Jest summary.
Before merging, a reviewer checks three things. That the webhook path is the only one using express.raw, so every other route still gets parsed JSON. That the unique Session id on Purchase makes a replayed checkout.session.completed a no-op rather than a second row. And that the price comes from STRIPE_PRICE_ID on the server, never from the request body. The session's transcript keeps every command the agent ran, so a reviewer who doubts a line of the description can find the step that printed it.
Run it on every customer project that needs payments
A services firm that adds payments to client apps can start one session per client repository from its own backend, with the client's repository in resources and the client's product details in the prompt. Send an Idempotency-Key built from the client and the job so a retried request returns the run already started instead of opening a second pull request.
Set a webhook URL on the API key and Rush POSTs run.completed when the turn ends, with session_url. Your server reads the session, finds the pull request on it, and posts the link to the client's channel. A run that ends run.failed posts that instead, and the transcript shows which step stopped it.
The use case this job follows, with its automation and trigger: Run an agent per customer task.
Why run it on Rush?
Testing a Stripe webhook on a laptop means a running database, the Stripe CLI logged in, a second terminal for stripe listen, and a signing secret pasted into a .env that nobody remembers to delete. The agent does all of that in its own sandbox: Postgres, the server, the forwarder and the trigger run side by side on a machine that exists only for this job, and the Stripe test key never touches a developer's shell history.
The pull request carries its own proof. The forwarded event, the 200 from the handler, the refused bad signature and the Jest run are the output of commands that ran, and the transcript keeps each one with its output. That is what a reviewer needs to merge payment code, and it is the same whether one app gets Checkout or a dozen run at once up to the key's concurrent run limit.
Frequently asked questions
Where does the Stripe test key come from?
Save it as a Project secret named STRIPE_SECRET_KEY with POST /o/{org}/secrets and start the session in that Project with POST /o/{org}/p/{project}/sessions, which takes the same body. The agent reads it as an environment variable in the sandbox. Use a test-mode key; the prompt never needs a live one.
Why does the webhook route have to come before bodyParser.json()?
Stripe signs the raw request body, and constructEvent recomputes that signature from the bytes it is given. Once bodyParser.json() has parsed the body, the original bytes are gone and every event fails verification. Registering the webhook path first with express.raw keeps its body as a Buffer while the rest of the API is unchanged.
Does it change my existing routes or auth?
No. It adds one route to routes.ts, one route in main.ts, and reuses auth.required as it is. The existing controllers, auth.ts and the JWT handling are left alone, and the pull request diff shows exactly which lines moved.
Can it handle subscriptions instead of a one-time price?
Yes, if the prompt says so. Change the prompt to a recurring price and ask it to record customer.subscription.created and customer.subscription.deleted as well, and the agent adds those events to the handler and triggers each one with the Stripe CLI before opening the pull request.