Percent-Encoding, Query Strings, and the Gotchas That Break Redirects and Webhooks
A developer's guide to URL encoding gotchas: the + vs %20 split, double-encoding in redirect chains, webhook URL corruption, and a fast debugging workflow.
Percent-Encoding, Query Strings, and the Gotchas That Break Redirects and Webhooks
URL encoding is one of those topics that looks solved until you're staring at a webhook that silently drops a parameter, or a redirect chain that loops because a % got encoded a second time. The rules are precise — RFC 3986 defines exactly 66 unreserved characters that never need escaping — but the edge cases show up in ways that don't produce visible errors, just subtly wrong behavior.
This is a guide to the specific problems I've hit in production, with real inputs and outputs so you can recognize the pattern before spending an hour on it.
What Percent-Encoding Actually Is
Every character that a URL can't carry literally gets replaced by % followed by two hexadecimal digits representing its byte value. A space is %20, an ampersand is %26, a hash is %23. For characters outside ASCII — emoji, Chinese, Arabic — the character is first encoded as UTF-8, and each resulting byte gets its own %xx pair. The Chinese character 好 is three UTF-8 bytes (E5 A5 BD), so it becomes %E5%A5%BD.
RFC 3986 divides characters into three groups: unreserved (letters, digits, -_.~), reserved (structural delimiters like / ? # & =), and everything else. Reserved characters are only safe when they're playing their structural role. When they appear as data, they must be escaped. That's the whole system. The complexity comes from where exactly you're in the URL.
The + vs %20 Split in Query Strings
Query strings have a legacy encoding format called application/x-www-form-urlencoded, which is what HTML forms use by default when submitted. In this format, a space encodes as + — not %20. Most server-side frameworks decode + back to a space when reading form fields, so you never notice.
The problem surfaces when you're building URLs by hand or logging raw request strings:
# What you typed into a form field:
search term with spaces
# What the browser sent (form submission):
?q=search+term+with+spaces
# What you might build in JavaScript using encodeURIComponent:
?q=search%20term%20with%20spaces
Both decode to the same string in most web frameworks. But if you paste the first URL into a context that only understands %20 — some API gateways, older proxy configs, certain CDN query-string rewriting rules — the + signs pass through literally, and the server sees "search+term+with+spaces" as the query value. The + is a valid character in the unreserved set, so nothing flags it as wrong.
I hit this in a third-party analytics integration. Our redirect URLs were built with encodeURIComponent (correct), but the analytics platform's URL-shortener encoded them again using the +-for-space form convention. The space in a campaign name turned into %2B — encoded plus — and the downstream report showed "Summer+Sale" instead of "Summer Sale".
When debugging, paste the raw URL from your network inspector into Toolora's URL Encoder / Decoder and decode it. It shows you the literal character the server receives, which is often different from what you expect.
Double-Encoding: the Redirect Chain Problem
Double-encoding is the most common cause of %25 appearing in a URL where you didn't put it. Here's the sequence:
- You have a value that contains a
%sign:100% organic - You encode it correctly:
100%25%20organic - Something in the redirect or proxy chain encodes it again, treating the
%in%25as a new character to escape - The value that arrives at the destination:
100%2525%2520organic - After one decoding pass:
100%25 organic— not the original
Real example. I was building a redirect system for localized URLs. The redirect rules lived in an nginx config, and each destination URL was stored already-encoded in a database. When nginx applied its own ngx_http_rewrite_module encoding before appending the redirect target, every % became %25. The destination page decoded the query parameter and showed literal %25 in the UI.
To check if a URL is double-encoded, look for %25 in the raw URL string. That's a percent sign that's been encoded. If the original data shouldn't contain a percent sign, something encoded twice.
# Single-encoded (correct):
?ref=summer%20sale%20%2650%25%20off
# Double-encoded (wrong):
?ref=summer%2520sale%2520%252650%2525%2520off
Use Toolora's URL Parser to pull the query string out cleanly and inspect each parameter in isolation. It decodes one layer at a time, which makes the double-encoded values visible immediately.
Webhook URLs: Where Encoding Breaks Silently
Webhooks are especially vulnerable because the URL is usually configured by a human, stored as a string, and then used by code that may or may not apply its own encoding before making the HTTP request.
Typical scenario: you configure a webhook endpoint with a query parameter that contains an API key or routing identifier:
https://api.example.com/hook?channel=sales&token=abc+def/xyz
The + and / in the token are not encoded. When your webhook delivery library (say, Python's requests, or Node's axios) builds the request, it may encode the URL — or it may send it verbatim, depending on how it was constructed. If it encodes, + becomes %2B and / becomes %2F. If it doesn't encode, the / may be interpreted as a path separator by the receiving server.
The safe rule: any webhook URL you configure by hand should have its query parameter values manually encoded before you paste the URL into the config field. Use + → %2B, / → %2F, & → %26. Never assume the delivery library will handle it correctly, because they differ.
To test before committing, run the raw URL through Toolora's URL Encoder / Decoder in component mode — it shows you exactly which characters are unsafe in a query value context.
Path Segments vs Query Parameters: Different Rules
One detail that trips up people new to URL encoding: path segments and query parameter values have different reserved character sets.
In a path segment, / is a delimiter, so it must be encoded as %2F if it's part of your data. But ? and & have no structural meaning in paths, so they're technically allowed — though encoding them is still safer. In query parameter values, ? and # need encoding (%3F, %23) because they end the query string or start the fragment. But / in a query value is allowed unencoded.
This matters when you're building a URL with a user-provided string that might go into either part:
// User input: "reports/Q2 & Q3"
// Correct encoding for a path segment:
encodeURIComponent("reports/Q2 & Q3")
// → "reports%2FQ2%20%26%20Q3"
// Correct encoding for a query value (/ can stay, but won't hurt):
encodeURIComponent("reports/Q2 & Q3")
// → "reports%2FQ2%20%26%20Q3" (same — encodeURIComponent is the safe choice for values)
encodeURIComponent is the right function for encoding values in both contexts because it encodes everything except the 66 unreserved characters. encodeURI is only for encoding complete URLs where you want the structural delimiters preserved — it deliberately skips / ? # & =.
A Fast Debugging Workflow
When a URL behaves unexpectedly, this sequence finds the problem in under five minutes:
- Get the raw URL from the network inspector (Chrome DevTools → Network → right-click request → Copy → Copy URL). This is what actually went over the wire, not what your code intended.
- Decode it once and look at the values. If they already look wrong, the encoding happened before transmission. If they look right decoded, the problem is in how the receiving side is decoding.
- Look for
%25— that's a double-encoding flag.
- Look for unencoded
&or=inside parameter values — that's a missing-encoding flag.
- Check
+signs — decide whether the context expects form encoding (where+= space) or strict percent-encoding (where+= literal plus).
The Toolora URL Encoder / Decoder handles all of this in one place: paste the raw URL, click decode, and each parameter shows up as a separate row with its actual value. For multi-URL analysis — checking a batch of redirect targets or webhook logs — URL Query Params Extractor lets you paste several URLs at once and extract every parameter across all of them.
URL encoding problems don't throw exceptions. They produce slightly wrong data that looks plausible at first glance. The debugging workflow above finds them before they spend a week corrupting analytics or silently failing webhook routing.
Made by Toolora · Updated 2026-06-29