Docs
Run Shakedown against your own checkout
Shakedown sends six sandbox-only test customers at your own PayPal checkout, webhook listener and support assistant. It grades each one from PayPal’s sandbox ledger, not from what your store says, and prints a receipt for every dollar that would have leaked.
Quick start
You need Node 22.18 or later and a PayPal sandbox app. Always use the scoped package name: a bare npx shakedown runs an unrelated package.
npx @shakedown-dev/cli preflight --target http://localhost:3000
npx @shakedown-dev/cli run --target http://localhost:3000
npx @shakedown-dev/cli reportpreflight checks your setup, the sandbox lock and your store. run sends the customers and writes the reports to .shakedown/. report opens the HTML report, or prints the last run as the terminal receipt, Markdown, JUnit or JSON.
Environment
Secrets come from the environment or .env.local, never from the config file, and Shakedown never prints them.
| Variable | What it’s for |
|---|---|
PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET | Your sandbox app. Without them, the checkout customers are skipped. |
SHAKEDOWN_PROBE_SECRET | 16+ characters, shared with your store’s read-only probe route. |
SHAKEDOWN_VERIFICATION_TOKEN | For a store that isn’t local: served at /.well-known/shakedown.txt. |
ANTHROPIC_API_KEY | Optional: only for --explain, and to read a refund policy written in prose. |
Exit codes
0 pass · 1 leaks · 2 inconclusive (strict mode) · 3 safety lock · 4 config · 5 the store isn’t answering.
What your store needs to answer
Shakedown talks to your store over HTTP. Leaky Llama, the demo store in this repository, is the reference implementation.
| Route | For |
|---|---|
GET /api/catalog | The products a customer can buy |
POST /api/checkout/orders | Open a checkout (creates the PayPal order) |
POST /api/checkout/orders/:id/capture | Capture it, as your checkout page would |
POST /api/paypal/webhook | Your webhook listener |
GET /api/probe/orders/:id | Read-only: what your store believes about an order. It must refuse any request without the x-shakedown-probe secret. |
POST /api/support/chat | The Policy Lawyer only: your support assistant |
Config
Shakedown loads the first shakedown.config.{ts,mts,js,mjs,json,yaml,yml} it finds. Unknown settings are an error, and so is anything that looks like a secret.
import { defineConfig } from '@shakedown-dev/cli'
export default defineConfig({
target: { url: 'http://localhost:3000' },
cast: ['double-clicker', 'cart-shuffler', 'echo', 'bouncer'],
seed: 2026, // the same seed replays the same customers and amounts
budget: { minutes: 10 },
})By default a run sends the four customers that need no AI, so it costs nothing beyond sandbox calls. Add policy-lawyer to test your support assistant, with your refund policy as rules in policy.
CI
The build fails on a leak (exit code 1), and comment keeps one scoreboard comment on the pull request up to date, using the workflow’s own token.
- run: npx @shakedown-dev/cli run --ci
env:
PAYPAL_CLIENT_ID: ${{ secrets.PAYPAL_CLIENT_ID }}
PAYPAL_CLIENT_SECRET: ${{ secrets.PAYPAL_CLIENT_SECRET }}
SHAKEDOWN_PROBE_SECRET: ${{ secrets.SHAKEDOWN_PROBE_SECRET }}
- if: always() && github.event_name == 'pull_request'
run: npx @shakedown-dev/cli comment
env:
GITHUB_TOKEN: ${{ github.token }}The console
The console is an AG Studio dashboard over every run: the Scoreboard, the cast, the leak waterfall, each finding with its evidence, and the ledger. Click a leak or a customer and the rest of the page follows. Its Triage agent answers questions from a summary computed in code, and hands everything else to AG Studio’s built-in agents. Its Claude spend has its own cap.
How it decides
The customers are scripts and the AI does the talking; code keeps the score. A check passes or fails from PayPal’s sandbox records: captures, refunds and the webhooks the store accepted. The same seed gives the same verdicts and amounts on every re-run, and a saved run can be judged again by newer graders without repeating a call. The model never decides whether money leaked.
Responsible use, and how each rule is enforced
- Sandbox only
- Every PayPal call goes through a sandbox lock that refuses any host but the sandbox API (
packages/paypal/src/sandbox-lock.ts), andPAYPAL_ENVmust besandboxor nothing runs. - Your integration only
- A target must be local or on a private network, or allow-listed and serving your verification token, before the first request (
packages/core/src/guards.ts). - Fictional customers
- The cast are scripted test characters. They pay with PayPal’s published sandbox test cards and use generated example.com addresses.
- Bounded by design
- Each campaign has a request and wall-clock budget, and every Claude call passes a spend gate with a hard cap that refuses before anything is sent (
packages/ai/src/gate.ts). - Outcomes, not playbooks
- Findings report what would have leaked, the evidence and the fix. See
RESPONSIBLE_USE.mdandSECURITY.mdin the repository.