Techniques 16 min read

How to Edit and Resend HTTP POST Requests in a Browser

Edit and resend an HTTP POST from DevTools: the Firefox inline editor, the Edge Network Console, why Chrome Replay XHR skips fetch requests, and the 25 headers Copy as fetch drops.

ST
Scraping.Pro Team
Data collection for business needs
Published: 25 February 2026

A product listing page fires one request when you click "load more". Right-click it in the Network panel, change "page": 2 to "page": 400, send it again, and in four seconds you know whether the API paginates past anything the interface will show you. The hard part is not the edit. It is finding that one request: in the HTTP Archive's July 2025 crawl, the median desktop home page issued 77 requests and the ninetieth percentile issued 185. Filtering the Network panel to Fetch/XHR usually cuts that to a handful.

Everything below was rechecked in August 2026 against Firefox 153, Chrome 151, the published Chrome DevTools source, Microsoft's Edge documentation, and each vendor's pricing page. Two claims in the earlier version of this article did not survive that check, and both are corrected in place rather than quietly dropped.

What resending actually buys you

Every meaningful action on a modern site is an HTTP request. Search, filter, log in, paginate, "load more". Each is usually a POST or a fetch carrying JSON or form data to an endpoint nobody documented for you. Replaying one with a changed parameter gives you three things.

  • Debugging without the UI. You stop clicking through five screens to reach the state that breaks.
  • Reverse-engineering the private API. This is the one that pays. Which parameters does the server actually read? Does a limit of 24 become 500 if you ask? Does an undocumented include= expand the response? Was the field missing from the rendered page in the JSON all along? Each answer costs one edit and one resend.
  • De-risking a scraper before you write it. Confirm the request by hand, then translate it to Python.

It is the shortest bridge between "I can see the data" and "I can fetch the data".

Firefox: the only browser with an editor in the Network panel itself

Firefox has had this longest and still does it best. Open DevTools with Ctrl+Shift+I (Cmd+Opt+I on macOS), switch to Network, reproduce the action, right-click the request.

The menu carries two separate items. Resend "resends the request as it was originally sent with no changes made." Edit and Resend "opens an editor enabling you to edit the request's method, URL, parameters, and headers, and resend the request." The request details documentation adds the field that matters most for a POST: editing mode covers "the method, URL, request headers, or request body of the request."

Change what you want, click Send, and the request goes out with your real cookies and your real session. You are testing as the logged-in user, not a blank client, which is why a Firefox resend works where the same request in a terminal does not.

One item in the same context menu deserves more attention: Use as Fetch in Console drops the request into the Console as a fetch() call, ready to edit as JavaScript. That is the Chrome workflow below, with the copying and pasting done for you.

Edge has one too, and most write-ups miss it

Correction, this article's previous version included. The claim that Chromium browsers offer no inline request editor is wrong for Edge, which ships a separate DevTools panel called the Network Console. Microsoft's documentation gives the same entry point Firefox users know: "Right-click the network request that you want to change and resend, and then select Edit and Resend." You can also open it cold from More tools > Network Console and compose a request from nothing.

It handles GET, HEAD, POST, PUT, PATCH, DELETE and OPTIONS, and saves collections in Postman v2.1 and OpenAPI v2 formats. Microsoft's page was last revised on 30 June 2025, and no experimental flag is needed. Edge is Chromium, so everything in the next two sections works there too, with this one extra option on top.

Chrome: Replay XHR is narrower than the guides say

Second correction. Chrome's Replay XHR does not work on fetch() requests, and every roundup that says "right-click an XHR or fetch request" is repeating an assumption nobody checked. In the DevTools branch that ships with Chrome 151, the menu item is gated on a single equality test:

typescript
static canReplayRequest(request: NetworkRequest): boolean {
  return Boolean(requestToManagerMap.get(request)) && Boolean(request.backendRequestId()) &&
      !request.isRedirect() && request.resourceType() === Common.ResourceType.resourceTypes.XHR;
}

DevTools classifies XMLHttpRequest traffic as XHR and fetch() traffic as Fetch. They are different resource types, which is exactly why the Network panel filter reads "Fetch/XHR" rather than "XHR". On a request your fetch()-based front end made, the Replay XHR item is simply absent. Select the row, press R, and nothing happens either. fetch is what most front-end code uses now, so this covers most pages.

That is being fixed. On the DevTools frontend main branch the item is renamed Resend and the gate becomes a set called FULL_FIDELITY_RESEND_TYPES, covering XHR, Fetch, Script, Stylesheet, Image, Media, Font, Wasm, Manifest, TextTrack and source maps. As of 10 August 2026 that has not reached stable, and Chrome's own Network features reference still documents the old name.

Either way, Replay XHR resends byte-identical. There is no editing. For that, Chrome gives you the clipboard.

Copy as fetch, and the 25 headers it drops

Right-click the request, choose Copy > Copy as fetch, paste into the DevTools Console. The snippet runs in the page's own JavaScript context, so the browser attaches your cookies for you.

javascript
fetch("https://example.com/api/search", {
  "headers": {
    "content-type": "application/json",
    "x-requested-with": "XMLHttpRequest"
  },
  "referrer": "https://example.com/search",
  "body": "{\"query\":\"laptops\",\"page\":2}",
  "method": "POST",
  "mode": "cors",
  "credentials": "include"
});

DevTools writes the options with JSON.stringify, so keys arrive quoted and the body arrives as an escaped string rather than an object. Edit the body, append .then(r => r.json()).then(console.log) to see the result, press Enter.

The copy is lossy, deliberately. DevTools filters the captured headers through a list of 25 names before writing the snippet: four internal HTTP/2 pseudo-headers plus 21 a page is not allowed to set. Cookie, Origin, Referer, Host, User-Agent, Content-Length, Connection, Accept-Encoding and DNT are all on it, and none survives the copy. The Fetch Standard defines these as forbidden request-headers, and MDN gives the reason plainly: "Modifying such headers is forbidden because the user agent retains full control over them." Anything beginning with Sec- or Proxy- is forbidden by prefix, covering the whole Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site, Sec-Fetch-User family from the W3C Fetch Metadata draft.

credentials is computed, not copied. DevTools writes "include" if the original request carried a cookie or an Authorization header, "omit" otherwise. That one line makes the snippet behave like the real request while carrying no cookie text at all.

The Node.js variant is different. Copy as fetch (Node.js) deletes mode, drops credentials, and writes your session cookie back into the headers object as a literal string. It works outside a browser precisely because it contains your session in plain text. Treat it as a secret.

One obstacle before any of this runs. Since Chrome 120, pasting into the Console trips a self-XSS guard: "Warning: Don't paste code into the DevTools Console that you don't understand or haven't reviewed yourself. This could allow attackers to steal your identity or take control of your computer. Type "allow pasting" below and press Enter to allow pasting." Type allow pasting, press Enter, and DevTools writes a synced setting named disable-self-xss-warning. Once per profile, not once per session.

Copy as cURL keeps everything, including your session

Copy > Copy as cURL is the opposite trade. It drops eight header names (accept-encoding, host, method, path, scheme, version, authority, protocol, plus content-length when there is a body) and passes everything else through. Your Cookie becomes a -b flag, every other header becomes -H, and the POST body becomes --data-raw. That last choice matters: unlike -d, --data-raw does not treat a leading @ in your JSON as a filename. If the captured request ran over plain HTTP, DevTools appends --insecure.

On Windows the menu shows two entries, Copy as cURL (cmd) and Copy as cURL (bash), with different escaping rules. Everywhere else there is one.

A cURL command is a credential. It contains your live session cookie, one paste away from a bug tracker, a shell history, or a support ticket. The industrial version of that mistake is the HAR file. In October 2023 Okta disclosed that an attacker with access to its support case system read customer-uploaded files, and its own security advisory spells out why that mattered: HAR files can contain "cookies and session tokens, that malicious actors can use to impersonate valid users." A copied cURL command is a one-request HAR with better ergonomics. Sanitise before sharing.

Safari: HAR export, and honest limits

Apple's Safari Developer help for the Network tab documents filtering, the details sidebar, and cache control. WebKit's own Network tab page documents one export path: "To export data in the table into an HTTP Archive (HAR) file, click Export or by press ⌘ S."

Neither documents a resend, an edit-and-resend, or a copy-as-cURL item. Whether a given Safari build carries one is not something the published documentation settles. The documented route works: export the HAR and convert it. curlconverter accepts HAR alongside raw cURL, targets more than 40 languages, and runs client-side, stating that it does "not transmit or record the curl commands you enter or what they're converted to." For a request carrying a session cookie, local conversion is the only kind worth using.

The gotcha: CSRF tokens and other single-use values

This is what stops a replayed request cold. Anything state-changing on a well-built site is protected by a token the server issued and expects back exactly once. Replay a captured request with yesterday's token and you get 403 Forbidden or an invalid-token error, with a response body that tells you nothing useful.

The OWASP CSRF prevention guidance names the patterns you will meet: the synchronizer token, the signed double-submit cookie, custom headers such as X-CSRF-Token, and the SameSite cookie attribute. Two details change how you replay. Token lifetime is the site's choice: "CSRF tokens should be generated on the server-side and they should be generated only once per user session or each request." A per-session token survives your editing loop. A per-request token is dead the moment you look at it, and every replay needs a fresh page load first. OWASP also warns that the naive double-submit cookie "is bypassable by an attacker who can write cookies on the target domain," which is why newer sites bind the token with an HMAC and a replay that copies only the cookie half fails.

Tokens are not the only value that rots. Watch for session cookies that rotate on every response, signatures computed over the payload, timestamps checked for freshness, and ASP.NET's __VIEWSTATE and __RequestVerificationToken, validated together.

SameSite is a browser rule, and only a browser obeys it. In the HTTP Archive's July 2025 data, 3% of first-party cookies set SameSite=Strict, 19% set Lax, 11% set None, and 66% set nothing and inherit the browser default. That governs a resend inside the browser. It means nothing for a cURL replay: you pasted the cookie yourself, and curl has no concept of a site to compare against. A request the browser refuses to send with credentials can succeed verbatim from a terminal.

To replay reliably, supply a fresh value for each dynamic field:

  1. Load the page, or its precursor request, first and read the current token from the response HTML, the JSON, or a Set-Cookie header.
  2. Keep the same cookie jar so the token and the session agree. Firefox's Edit and Resend and the DevTools Console run in your live session, which is why they usually just work.
  3. Put the token where the site expects it: a hidden field, a JSON property, or a header. Copy the placement from the captured request rather than guessing.

In code that becomes a two-step fetch, which is most of what scraping login-protected sites amounts to.

Where each method breaks

CORS. The copied fetch carries mode: "cors". In the Console of its own page it is same-origin, so nothing happens. Run it from another tab or any origin that does not match and the browser blocks it, or forces a preflight the API never had to answer. The failure is a network error with no status code, which reads like an outage.

The wrong execution context. The Console's context dropdown defaults to the top frame. If the request came from an iframe, a snippet pasted there runs on the wrong origin with the wrong cookies.

Service workers. A fetch() from the page goes through the page's service worker, which may rewrite the URL, add headers, or answer from cache. Your resend then tests the service worker, not the API.

TLS and HTTP/2 fingerprints. Copy as cURL reproduces headers, not handshakes. Anti-bot systems fingerprint the TLS ClientHello and the HTTP/2 SETTINGS frame, and curl's differ from Chrome's no matter how many headers you copy. That is the premise of curl-impersonate, whose maintained fork reached 2.0.0 on curl 8.21.0 by "compiling with BoringSSL, Google's TLS library, which is used by Chrome and Safari" and "changing the settings that curl uses for its HTTP/2 connections." Its Python bindings, curl_cffi, are the usual route to browser-shaped requests from a script.

Stale metadata headers. DevTools drops Sec-Fetch-* from a copied fetch because a page cannot set them. It keeps them in a cURL command, where curl sends them verbatim. A terminal request announcing Sec-Fetch-Site: same-origin with no browser behind it is an inconsistency a WAF can key on.

Bodies that do not copy. If the original body was a Blob or a ReadableStream there is nothing to copy and the menu items are disabled. File uploads land here.

Six ways to fail, and none of them produce a helpful error message.

Beyond DevTools: proxies and API clients

When one request turns into forty variants, or the traffic you need comes from a phone, DevTools stops being the right shape. Prices were read from each vendor's own pricing page on 10 August 2026.

Tool What it is for Price
mitmproxy Scriptable proxy; HTTP/1, HTTP/2, HTTP/3, WebSockets. 12.2.3 shipped 12 May 2026 Free, MIT
Charles Desktop proxy, mature mobile support $50 per licence, $40 at 5+, $30 at 10+
Fiddler Everywhere Telerik's cross-platform successor to Fiddler $7, $13 or $37 per user per month, annual
Fiddler Classic Windows-only, the one everybody remembers Free, with a catch
Postman Request editor with history, environments, variables Free tier; $9, $19 or $49 per user per month
Insomnia Same category; local Scratch Pad needs no account Free tier; Pro $12, Enterprise $45
Bruno Git-native, files on disk instead of a cloud workspace Open source $0; Pro $6, Ultimate $11
Hoppscotch Browser-based, MIT-licensed, self-hostable Free to self-host
Requestly Extension that rewrites live requests and headers Free tier; Pro $12 per user per month

The catch matters before you download it. Telerik's own page now labels Fiddler Classic "Non-commercial use only | Maintenance mode | Community support | Free" and says it "is not in active development and offers no commitments for releases, patches or tech support." The free Windows sniffer a decade of tutorials recommend still works, but using it at your job is outside its licence. Our older Fiddler review predates that change. mitmproxy carries no such restriction.

A proxy earns its keep on three things DevTools cannot do: it captures traffic from a phone or another machine, it breakpoints a request mid-flight so you can edit it before it reaches the server, and it sees requests made outside any tab. Reading HTTPS costs a certificate install. The broader sniffer overview covers where each sits.

From a working replay to a scraper

Once an edited request returns what you want, translating it is mechanical. curlconverter does the conversion locally; Postman imports a raw cURL command and generates a client snippet in another language.

python
import requests

session = requests.Session()
# 1) load a page first if you need a fresh token or cookies
session.get("https://example.com/search")

# 2) replay the (edited) POST within the same session
resp = session.post(
    "https://example.com/api/search",
    json={"query": "laptops", "page": 2},
    headers={"X-Requested-With": "XMLHttpRequest"},
)
print(resp.status_code)
print(resp.json())

Written against Python 3.11 and requests 2.x. The Session object does the load-bearing work, holding the cookie jar across both calls. That covers the cookie half of the CSRF dance. If the token arrives in the page HTML rather than a cookie, parse it out of the first response and attach it to the second.

When the target checks more than headers, keep the replay inside a real browser. Playwright's API request context "uses the same cookie jar as its BrowserContext" and will "populate request cookies from the context and update context cookies from the response", so you can drive the login in the browser and fire the edited POST through the same session, with the browser's TLS stack underneath. That is the cheapest answer to fingerprinting short of patching curl.

For the wider picture, see API scraping. At volume, add rotating proxies and retry logic around the request you verified by hand.

What changes at ten thousand pages

A hand replay has one session, one token, and a human watching. A crawl has none of those.

Tokens become a budget line. A per-request token means every real request needs a preceding page load, doubling your request count and your rate-limit exposure. A token fetch costing one extra second per page adds close to three hours across ten thousand pages.

Sessions expire mid-run. A cookie jar that survived an hour of manual testing will rotate or be invalidated under load. Build the refresh path first and make the failure loud. A silent 403 that your code reads as an empty result set produces a clean-looking dataset with a hole in it.

Consistency starts to matter more than correctness. One request with slightly wrong headers succeeds. Ten thousand identical requests with slightly wrong headers are a fingerprint. Where that turns into a full-time job rather than a scripting problem, a managed extraction setup is the usual answer, because the cost sits in session handling rather than parsing.

Editing a parameter and resending is a technique, not a permission. The precedent worth knowing is United States v. Auernheimer, decided by the Third Circuit on 11 April 2014, docket 13-1816. The conduct was exactly this: a script that "would repeatedly access the AT&T website, each time changing the ICC-ID in the URL by one digit," harvesting email addresses from a public endpoint. The conviction was vacated on venue, not because the technique was found lawful.

The second case is Van Buren v. United States, decided 3 June 2021 by 6-3, which narrowed the federal computer-fraud statute's "exceeds authorized access" clause to a gates-up-or-down question: you either can or cannot reach a given area of a system. Changing an ID in a request you are authorised to make, so as to read a record you are not, is a gate. Changing a page number on your own search is not.

Use it responsibly. Replay requests against systems you own, test environments, or data you are permitted to access. Editing an identifier to reach another user's record is the same act in DevTools as in code, and the tooling does not change the law.

Summary

Firefox is still the only browser with a request editor inside the Network panel, and Edge has an equivalent in a tool most articles forget. Chrome's Replay XHR is XHR-only in the shipping build, which rules out most modern front ends until the rename to Resend reaches stable. Copy as fetch discards 25 headers and rebuilds credentials for you, which is why it works in the Console and fails everywhere else. Copy as cURL keeps everything, including the session cookie you should not paste into a ticket. The reliable blocker is the single-use value, whether a token, a nonce, a signature or a timestamp, and the fix never changes: fetch it fresh in the same session, immediately before the request that needs it. Get that loop right by hand and the code is a transcription.