Double URL Encoding

Double URL encoding happens when a string is encoded twice, turning %20 into %2520. Learn how it starts, how to detect the %25 fingerprint, and how to decode it safely.

ZERO UPLOAD · ALL LOCAL
  1. The tool opens in DECODE mode. Paste a percent-encoded URL or string into the input field.
  2. The decoded output appears instantly. If the input contains a query string, each parameter is parsed into an editable table.
  3. Edit any value in the parameter table to see the rebuilt encoded query string update in real time.
  4. Switch to ENCODE mode to convert text to three encoding variants: encodeURIComponent, encodeURI, and form encoding with + for spaces.
  5. Click Copy next to any result to copy it to your clipboard.

Worked examples for this use case

A space encoded twice

Before
%2520
After
%20, then a space

The first decode yields %20; a second decode yields the space.

A path slash encoded twice

Before
a%252Fb
After
a%2Fb, then a/b

Two decode passes recover the original a/b.

Detecting it in a log line

Before
redirect=https%253A%252F%252Fexample.com
After
https%3A%2F%2Fexample.com, then https://example.com

The %25 prefixes flag double encoding at a glance.

A legitimate literal percent

Before
50%25off
After
50%off

Here %25 is correct and decodes once to a literal percent. Not every %25 is double encoding.

INPUT

DECODED OUTPUT
ENCODED OUTPUT
encodeURIComponent
encodeURI
Form encoding (+)

Double URL Encoding: Why %2520 Appears and How to Fix It

Double encoding is what happens when a value passes through an encoder one time too many. Each pass replaces every percent sign with %251, so a space that became %20 on the first pass becomes %2520 on the second. The data is not corrupted, but it is now wrapped in an extra layer, and a single decode returns %20 instead of the space you wanted2.

Double encoding usually creeps in when two layers of a system each try to be helpful: a client encodes a value, then a framework or proxy encodes the already-encoded string again3. Consequently, the classic symptoms are literal %20, %3A, or %2F sequences showing up in decoded output, or their doubled forms %2520 and %252F appearing in logs3. The CapyToolkit decoder lets you peel the layers one at a time until the value stops changing, which is the reliable way to recover the original4.

How a value gets encoded twice

Double encoding is rarely deliberate; it emerges where responsibilities overlap. Picture a browser that encodes a query value with encodeURIComponent, producing %20 for each space5. That encoded string then flows into a server-side routine, or a redirect builder, that assumes its input is raw and encodes it again. Neither layer is wrong in isolation, because each one sees only the string it received and applies the rule it was designed for.

Where the extra layer gets added

Consequently, the % in %20 becomes %25, and the value now reads %25201. The same thing happens when a URL is stored, retrieved, and re-encoded before reuse, or when an API gateway re-encodes a path it forwards, which explains why %20 turns into %2520 without anyone touching the value by hand. From the machine's side nothing is wrong, because each encoder did its job on the input it received. The trouble is that the two encoders disagreed about whether the string was already encoded. Naming which layer owns encoding, and encoding exactly once there, prevents the whole class of bug before it starts6. Teams that split URL handling across a frontend, an API gateway, and a backend service are especially prone to this, since no single layer can see the whole pipeline.

The %25 fingerprint that reveals it

You detect double encoding by looking for percent signs that should not survive a decode. A correctly decoded value contains no %XX sequences at all, because decoding resolves them into characters7. The reason this works is that a single clean decode turns every valid escape into its literal byte, leaving nothing percent-shaped behind8, so any remaining % after one pass is proof of extra layers. That is why a single clean decode should leave the string fully human-readable, and if you still see percent escapes the input was encoded more than once by a layer that did not know it was receiving encoded data.

Reading the doubled escapes

When you decode a string once and the output still holds %20, %2F, or %3A, the value was encoded twice: the first decode stripped the outer layer and revealed the inner one9. Equally, seeing %2520 or %252F in a raw URL or a log line is the direct fingerprint, since %25 is an encoded percent sign and the trailing digits spell the original escape9. Furthermore, a value that needs three or more passes before it becomes clean text points to triple encoding, which happens in pipelines with many hops10. The CapyToolkit decoder shows the output of each pass, so you can count exactly how many layers wrap the value.

Recognizing the %25 fingerprint on sight, rather than decoding blindly, saves time when you are scanning a long access log for the source of a malformed redirect. It also helps you avoid false positives: a legitimately encoded percent sign, such as the one in 50%25off, should not be confused with the outer layer of a double-encoded string, because only the latter reveals an extra percent layer when decoded once11.

Decoding layer by layer

The safe fix is iterative decoding, not a single guess. Paste the suspect value into DECODE mode and read the result: if it still contains percent-escapes, decode that output again, and repeat until the text stops changing2. Each pass removes one layer, and the stable result is the original value.

Conversely, resist the urge to strip %25 with a find-and-replace, because that damages legitimate %25 sequences meant as literal percent signs7. Once you know how many layers exist, fix the source by choosing a single owner for encoding and removing the redundant pass6. The decoder never transmits your input, so you can safely unwind double-encoded production URLs, tokens, or redirect targets while you trace where the extra encoding entered the pipeline. Documenting which layer owns encoding, once you find it, also prevents the next engineer who touches that code from reintroducing the same redundant pass.

When one layer is enough

With small inputs the fix is always the same: pick one place in the pipeline and encode there. If a client is responsible, the server decodes raw values and never re-encodes. If a backend owns encoding, the frontend should pass the raw value without percent-encoding it first11. This boundary is what matters, because the moment two layers both try to encode, they each need to know whether the other already touched the string, which is exactly the ambiguity that produces %2520 in production.

Beyond fixing the immediate bug, write the ownership rule into your codebase comments or README. Encoding happens at the boundary, not inside shared utilities, because shared utilities are the most likely place to be called twice by accident12. Adding a single assertion that checks whether a value already contains %25 before encoding it is also a practical safeguard, because it turns a silent double-encoding bug into an obvious failure in development rather than a mysteriously mangled redirect in production.

When to use this

Turn to this when decoded values still show %20 or %2F, when logs contain %2520, or when a redirect target arrives mangled10. Paste the value into the CapyToolkit decoder and decode repeatedly until the output stabilizes, counting the layers as you go4. Use that count to locate the redundant encoding step in your pipeline, then encode exactly once at a single owning layer so the double-encoding cannot return5. This is especially useful when debugging a multi-hop redirect chain, where each hop is a candidate for having added an unwanted extra layer12.

Examples

A space encoded twice

Before
%2520
After
%20, then a space

The first decode yields %20; a second decode yields the space.

A path slash encoded twice

Before
a%252Fb
After
a%2Fb, then a/b

Two decode passes recover the original a/b.

Detecting it in a log line

Before
redirect=https%253A%252F%252Fexample.com
After
https%3A%2F%2Fexample.com, then https://example.com

The %25 prefixes flag double encoding at a glance.

A legitimate literal percent

Before
50%25off
After
50%off

Here %25 is correct and decodes once to a literal percent. Not every %25 is double encoding.

Sources
  1. 1.

    IETF, "Percent-Encoding," RFC 3986 Section 2.1, datatracker.ietf.org, January 2005. https://datatracker.ietf.org/doc/html/rfc3986#section-2.1

  2. 2.

    Mozilla Developer Network, "decodeURIComponent()," developer.mozilla.org, accessed July 2026. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent

  3. 3.

    OWASP Foundation, "Double Encoding," owasp.org, accessed July 2026. https://owasp.org/www-community/Double_Encoding

  4. 4.

    WHATWG, "URL Standard," url.spec.whatwg.org, accessed July 2026. https://url.spec.whatwg.org/

  5. 5.

    IETF, "When to Encode or Decode," RFC 3986 Section 2.4, rfc-editor.org, January 2005. https://www.rfc-editor.org/rfc/rfc3986.html#section-2.4

  6. 6.

    Ecma International, "ECMAScript 2026 Language Specification," tc39.es, June 2026. https://tc39.es/ecma262/#sec-decodeuricomponent-encodeduricomponent

  7. 7.

    Mozilla Developer Network, "encodeURIComponent()," developer.mozilla.org, accessed July 2026. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent

  8. 8.

    IETF, "Reserved Characters," RFC 3986 Section 2.2, datatracker.ietf.org, January 2005. https://datatracker.ietf.org/doc/html/rfc3986#section-2.2

  9. 9.

    OWASP Foundation, "Path Traversal," owasp.org, accessed July 2026. https://owasp.org/www-community/attacks/Path_Traversal

  10. 10.

    WHATWG, "application/x-www-form-urlencoded," HTML Standard, html.spec.whatwg.org, accessed July 2026. https://html.spec.whatwg.org/multipage/forms.html#url-encoded-form-data

  11. 11.

    Ecma International, "ECMAScript 2026 Language Specification," tc39.es, June 2026. https://tc39.es/ecma262/#sec-encodeuricomponent-uricomponent

  12. 12.

    IETF, "Unreserved Characters," RFC 3986 Section 2.3, rfc-editor.org, January 2005. https://www.rfc-editor.org/rfc/rfc3986.html#section-2.3

FAQ