How to Decode a Double-Encoded URL, One Layer at a Time

A string that still contains %XX after decoding may have another percent-encoding layer—or it may be literal data. Inspect the result before decoding again.

Quick answer: Decode one percent-encoding layer, inspect the intermediate text, and continue only when the next layer is clearly intended. If the resulting escapes represent bytes in a legacy character set such as Shift_JIS, convert those bytes with that charset after removing the percent layer. Do not repeatedly decode an unknown URL until it “looks right.”

URL decoding has two separate jobs that are easy to mix up: turning %XX triplets back into bytes, then interpreting those bytes with the correct character encoding. A link can be percent-encoded more than once, and text from an older Japanese site may use Shift_JIS rather than UTF-8. Each step has to match how the value was created.

See the order at a glance

Decode a double-encoded URL in two deliberate stagesFirst remove one percent-encoding layer to reveal percent byte escapes. Then turn the bytes into text using the original character set, here Shift JIS.Follow the layers in reverse orderSTORED STRING%2583%258A%2550A second layer escaped each %1 · REMOVE ONE % LAYER%83%8A%50Now the original byte escapes show2 · INTERPRET AS SHIFT_JISリPBytes become readable charactersExample is independently reproduced below; use the source system’s actual charset, not a guess.

A verified Shift_JIS example

This example uses the text リP. Its Shift_JIS bytes are 83 8A 50. Percent-encoding those bytes once, then encoding the percent signs for a second URL layer, produces this string:

Twice encoded%2583%258A%2550
After one decode%83%8A%50
Bytes, then Shift_JIS83 8A 50 → リP

I reproduced this round trip locally with Python’s standard urllib.parse and shift_jis codec. The character result was checked as Unicode code points U+30EA U+0050, matching リP. This proves this constructed example—not the encoding of any particular third-party website.

For your own string, keep a copy of the original. Remove one percent layer and inspect the output. If it now contains valid %XX byte escapes, identify the source charset from the application or site that created it. Only then convert those bytes using that charset.

In Python, the same two operations can be kept explicit so you can inspect the bytes between them:

from urllib.parse import unquote, unquote_to_bytes

encoded_twice = "%2583%258A%2550"
one_layer = unquote(encoded_twice)  # "%83%8A%50"
raw_bytes = unquote_to_bytes(one_layer)  # bytes 83 8A 50
text = raw_bytes.decode("shift_jis")  # "リP"

Use this only when the source really used Shift_JIS. If the source charset is unknown, the correct decoded characters are unknown too.

How to tell whether another decode is justified

1Save the originalKeep an unchanged copy so you can compare every step.
2Decode one layerCheck whether escapes such as %25 became new %XX triplets.
3Check bytes and charsetUse the source application's documented charset; stop if it is unknown.
Checklist before decoding another layerPreserve the original, compare the intermediate output, confirm that valid percent byte triplets remain, and identify the source charset. If any check fails, stop and keep the intermediate result.Before you decode againDoes the intermediate still contain valid %XX byte escapes?Do you know which layer your application added?Do you know which character set turns the resulting bytes into text?If any answer is “no”Stop. Keep the intermediate result;ask the source system for its format.

Common traps

What you seeWhat it may meanSafer next move
%25 changes to %A percent sign from another encoded layer was exposed.Inspect the full intermediate string; do not decode again automatically.
Valid %XX groups remain, but text is garbledThe bytes may use a different character set than UTF-8.Confirm the source charset before converting the bytes.
%2F, %3F, or %26 appearsDecoding may reveal a slash, question mark, or ampersand that changes URL structure.Decode a component separately; do not alter a full URL’s delimiters unintentionally.
A percent sign is followed by non-hex textThe percent may be literal text or the string may be malformed.Keep the source and handle it as text; do not force a second decode.

Decode a URL value without changing the rest of the link

If you only need to inspect a normal UTF-8 URL value, open the PercentURL URL Decoder. For a full URL, use URL Encode and Decode and choose the relevant mode. These browser tools do not infer an unknown legacy character set; use the source application’s charset information for that step.

Common questions

Why does my URL still contain percent signs after decoding?

Those signs may be part of another percent-encoding layer, or they may be literal data. Decode one layer and inspect the result before deciding what it means.

Can UTF-8 decode every percent-encoded string?

No. Percent triplets represent bytes, and those bytes need a character encoding. Modern web examples often use UTF-8, while some older data may use another charset. Use evidence from the system that produced the value.

Should I keep clicking decode until I can read it?

No. Each decode changes the string and may expose URL delimiters. Preserve the original and stop when you cannot identify the next layer or charset.

References