An HTTP response playground for browser / web-security testing. Build any response from URL params, save it as a share, and capture incoming requests.
Every URL on the site can serve a custom HTTP response. The response is described entirely in the query string — no signup, no save required. Use this when you want a one-off fixture: a 302 to your attacker, an HTML page with a specific CSP, a JSON endpoint that returns slowly.
https://xss.cool/?status=302&redirect=https://example.com&h[X-Frame-Options]=DENY
This serves a 302 to example.com with an extra X-Frame-Options: DENY header. Tack &dryrun=1 on the end to see exactly what would be sent without firing the response.
| Param | What it does |
|---|---|
status | Status code, 200–599 (default 200). Aliases: statuscode, code. |
delay | Delay the response by N ms (max 300000). |
chunks + chunkdelay | Send the body in N pieces (1–64) with a pause of M ms (max 60000) between them. Chunked transfer-encoding, so the client sees each piece as it lands. Good for timing oracles. |
redirect | Sets Location. Alias: redir. Pair with status=302. |
html / script / js / text / json / body | Body. First defined wins (in this order). All sent raw and pick the matching Content-Type — except script, which wraps in <script>…</script>. html and body are functionally identical; body just sits at the end of the precedence chain. |
h[Header-Name]=value | Custom header (qs-style nesting). Repeat the same key for multiple values. Aliases: header[…], headers[…]. h[Content-Type] overrides the default; h[Content-Length] is passed through untouched. |
dryrun=1 | Don't send the response — return JSON describing what would have been sent. Also works on /s/<id>. |
The home page is a live editor that builds the URL above as you type. Status, delay and body type sit next to the tabs; the body editor (Monaco) handles HTML / JS / JSON with syntax highlighting and has a right-click encode/decode menu (URL-encode, base64, etc). Switch between Body, Headers, and Captures with the tabs (⌘1 / ⌘2 / ⌘3; ? for the full shortcut list).
The editor is either a scratch document or an open share. Save (⌘S) creates a share from scratch and moves the address bar to /e/<id>; on an open share it updates in place. Save as new forks. New clears the editor (undoable from the toast). The Domain switcher in the topbar picks which host the generated URLs print — useful for cross-origin tests (see below).
A share is a saved editor state. One id, two views:
/s/<id> — the replay URL. Renders the saved response every time. Hand this to a third party / writeup / test target./e/<id> — the editor URL. Opens the editor pre-filled with the saved state. Hand this to a collaborator; if they are not the owner, Save creates their own copy.The Shares menu in the topbar lists everything you've saved with copy, open, rename, submit-to-library and delete. Shares get a name automatically (the page title or first line); click the name to rename.
The Captures tab creates a webhook-sink endpoint at /c/<id>. Anything that hits it (any method, any subpath) is logged with method, path, headers, body (1 MB cap), and source IP. The panel streams new requests live via Server-Sent Events.
Use it for exfil targets, oracle pings, or any "did this fire?" check. Captures expire after 7 days. The Body editor toolbar has an "Insert capture URL" button that pastes a capture URL at your cursor.
The platform serves multiple host domains (xss.cool, ctf.baby, …) from one backend. Every share and capture is reachable on all of them. To set up a cross-origin PoC without standing up your own attacker server:
https://xss.cool/s/<id>.https://ctf.baby/?html=….frame-ancestors) even though they share a backend.Whoever creates a share or capture owns it. Browsers carry ownership in the xsscool_owner cookie; scripts send the same token as Authorization: Bearer <token>. Get your browser's token from the ⋯ → API token menu, or mint a fresh one with POST /api/token.
The xsscli CLI wraps the whole API. Install it from this deployment (Node 26+ required):
curl -fsSL https://xss.cool/cli/install.sh | sh # or: npm i -g https://xss.cool/cli/xsscool-cli.tgz
xsscli login <token> # or: xsscli token new
xsscli share '<h1>hi</h1>' --status 418 --header 'X-A: 1' --name demo
xsscli list · xsscli get <id> · xsscli update <id> --status 500 · xsscli rm <id>
xsscli dryrun 'https://xss.cool/?status=302&redirect=https://example.com'
xsscli capture new · xsscli capture tail <id> # streams requests as JSON lines
xsscli capture rm <id|url> -f # delete a sink and its log
--json for machine-readable output and --host to target another deployment or domain.For machine consumers (LLMs, scripts, integrations) the canonical schema lives at /api/schema.json (versioned JSON) and the terse summary at /llms.txt. Errors are JSON { error, issues?, hint? }.
| Endpoint | Notes |
|---|---|
POST /api/shares | Body: { state, name?, ttlDays? }. Returns the share with absolute replayUrl / editorUrl. |
GET /api/shares/mine | Your own shares (owner token). |
GET /api/shares/:id | One share with its state. Public; owned tells you if you can edit. |
PATCH /api/shares/:id | Owner only. Body: { state?, name?, ttlDays?, submittedPayloadId? }. |
DELETE /api/shares/:id | Owner only. |
GET/POST /api/token | Current owner token / mint a fresh one. |
POST /api/captures | Returns { id, captureUrl, streamUrl, requestsUrl, expiresAt }. |
ANY /c/:id (and subpaths) | Capture sink. Body capped at 1 MB. |
GET /c/:id/stream | SSE feed of incoming requests. |
GET /api/payloads | Public community-curated payload templates. |
GET /api/config | { domains: string[] } — the public hosts this deployment serves. |
ttlDays. Captures expire after 7 days.For a one-screen reference suitable for LLMs, see /llms.txt.