DevTools Breakpoints for Capturing Runtime Values
When static analysis fails, breakpoints win. Five breakpoint types, when to use each, and the disciplined workflow of pause-inspect-step.
What you’ll learn
- Use line, conditional, DOM, XHR, and event listener breakpoints.
- Inspect scope, call stack, and arguments at a breakpoint.
- Step through code to follow data flow.
- Save runtime values for replay in your scraper.
When the value you need is computed at runtime, a dynamically constructed signature, a hash assembled from multiple sources, a header derived from page state, grep won't find it. Breakpoints will.
This lesson is the operator's manual.
The five breakpoint types
- Line breakpoint, pause on a specific line of code.
- Conditional breakpoint, pause only if a condition is true (e.g.
x === 'BTC'). - XHR/fetch breakpoint, pause whenever a request matches a URL substring.
- DOM breakpoint, pause when a DOM element is modified, added, or removed.
- Event listener breakpoint, pause on a specific event type (click, scroll, keypress).
For scrapers, XHR breakpoints are the most useful, they're how you find code that builds requests.
Setting an XHR breakpoint
Sources → right sidebar → expand "XHR/fetch Breakpoints" → click the + → enter a URL fragment like /api/sign or signature.
Now every fetch or XHR whose URL contains that string pauses execution.
Trigger the request from the page. DevTools pauses. The Call Stack panel shows the JS that issued the request. Walk up the stack to find the function that built the request, that's where the signing logic lives.
A worked example, HMAC signature
Imagine you're reverse-engineering the lab at /challenges/api/auth/hmac-signed:
- Set an XHR breakpoint on
signatureor on the endpoint URL. - Trigger a signed request.
- DevTools pauses. Call Stack shows:
fetch (anonymous)
sendRequest (apiClient.js:142)
signRequest (signer.js:88) ← this is the one
handleSubmit (form.js:32)
- Click
signRequestframe. Sources jumps to the signing function. - Scope panel shows local variables:
body,timestamp,secret,canonicalString,signature. Read them. - Now you know:
- The exact canonical string format.
- The exact secret value.
- The exact algorithm (probably
crypto.createHmac('sha256'...)).
- Reproduce in Python (lesson 3.21).
This workflow turns "I have no idea how this is signed" into "I have the source code" in 5 minutes.
Conditional breakpoints
A regular breakpoint pauses every time. For high-frequency code, that's unusable. Conditional breakpoints only pause when your condition is true.
Right-click a line number → Edit breakpoint → enter productId === 1. The breakpoint fires only for product 1, easier to inspect.
Useful conditions:
header['Authorization'] !== undefined, only when auth is set.result.error, only when an error occurs.Date.now() > 1700000000000, only after a specific time.false, never pauses but DOES execute the condition expression, so you can sneakconsole.log(...)into the condition. Effectively a logpoint workaround on older browsers.
DOM breakpoints
Less common for scrapers, but useful when the value you want appears as a DOM mutation rather than a network call.
Elements panel → right-click an element → Break on → "Subtree modifications" / "Attribute modifications" / "Node removal."
Use case: a JS function injects a CSRF token into a form's hidden input. Setting a DOM breakpoint on the input fires when the token is set, letting you see who set it.
Stepping through code
Once paused:
- Step over (F10), execute the current line, don't enter functions.
- Step into (F11), enter the next function call.
- Step out (Shift+F11), finish the current function, pause at its caller.
- Resume (F8), continue execution.
For tracing signing logic, "step into" repeatedly until you reach the actual crypto.hmac call. Don't step over functions that might be the signing routine.
Inspecting state
Three panels matter while paused:
- Scope. Local variables at the current frame. Includes
Local,Closure,Global. Hover values to expand. - Call Stack. Functions that led here. Click any frame to switch context.
- Watch. Add specific expressions to evaluate continuously, e.g.
JSON.stringify(body),secret.length.
In the Console while paused, you can evaluate any JS in the current scope:
> body
{...}
> Object.keys(headers)
["Content-Type", "Authorization", "X-Signature"]
> btoa(JSON.stringify(body))
"eyJh..."
Saving the snapshot
Once you've captured the runtime values, save them to file or just to a comment in your scraper:
# Captured from DevTools 2026-04-12:
# - secret = "catalog108-hmac-secret"
# - canonical = METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + BODY
# - signature = hmac_sha256(secret, canonical).hex()
Now you don't need to re-pause the page each time. The recipe is written down.
Limitations and pitfalls
- Source maps make it easier; their absence makes it harder. Without source maps, you're reading minified names like
e.f.g. - Anti-debug detection. Some sites detect DevTools and bail out. Common defenses: infinite
debuggerstatements, eval() obfuscation. Workarounds: Firefox (often less aggressive detection), Chrome withDisable JavaScript debugger statementsset, or a stripped-down user.js script. - Web Workers. Workers run in separate threads with their own DevTools instance. Switch to the worker's Sources panel.
- Service Workers. Same, separate panel under Application.
Catalog108 lab
The /challenges/api/auth/hmac-signed endpoint is the canonical place to practice. The signing function lives in a JS file the page loads. Use:
- XHR breakpoint on the endpoint URL.
- Trigger a signed request.
- Walk the call stack.
- Find the function that builds
X-Signature. - Read the canonical string format and secret.
- Reproduce in Python (lesson 3.21).
A more advanced trick, modifying the page mid-pause
While paused, you can edit live state from the Console:
> secret = "the_other_secret"
> // resume execution
The signing now uses your replaced value. Tests whether the server actually validates the secret you thought it did.
This is the same trick as setting a debugger override; great for confirming your hypothesis about which value is load-bearing.
When breakpoints aren't enough
Sometimes the signing logic runs in a Web Worker, or uses Wasm, or is heavily obfuscated. In those cases:
- mitmproxy (lesson 3.47) intercepts the HTTPS at the network level, you don't care about the JS at all.
- Frida / Cycript for runtime instrumentation (mobile apps).
- Last resort: brute-force or accept the constraint.
But for ~95% of browser-based signing logic, the DevTools breakpoint workflow works.
Hands-on lab
On /challenges/api/auth/hmac-signed: set an XHR breakpoint, trigger a signed request, walk the call stack, find the signing function, capture the canonical string format. Now compare against your Python implementation from lesson 3.21. Confirm your scraper produces matching signatures. You've completed the round trip: reverse-engineer the JS, reproduce in Python, verify equivalence.
Hands-on lab
Practice this lesson on Catalog108, our first-party scraping sandbox.
Open lab target →/challenges/api/auth/hmac-signedQuiz, check your understanding
Pass mark is 70%. Pick the best answer; you’ll see the explanation right after.