TLDR
If you have WordPress + Cyrillic in URLs + users opening links via Viber on iOS → broken links, then:
- Detect iOS user-agent
- Look for %25 in REQUEST_URI
- Decode twice
- Redirect once to the decoded version
You probably saw it: you send someone a WordPress link with Cyrillic characters, they open it from Viber on an iPhone or iPad, and… the URL breaks. It has extra %25… parts. Basically, it’s double encoded. Frustrating, but there’s a neat fix.
I ran into this recently. Here’s what I discovered + how to patch it.
What’s going on ?
- When a link has Cyrillic (or other non-ASCII) characters, it is URL-encoded once, converting each character into %D0… etc.
- Viber on iOS seems to encode again, so % becomes %25, turning %D0%BF… into %25D0%25B….
- WordPress doesn’t recognize that double-encoded string, so you get 404s or “not found” errors.
Why only on iOS / Viber ?
- Android / many browsers handle the URL just once: encode → decode correctly.
- iOS + Viber seems to introduce this extra layer.
- The user-agent on the request offers a way to detect it (
iPhone/iPad).
The solution: Normalize the request URI
I found out a small function in a Stack Overflow post comment by Bobby Iliev that does exactly what I needed.
- Detect if the visitor is on iPhone or iPad (via
$_SERVER['HTTP_USER_AGENT']). - Check if the request URI has
%25→ a sign of double encoding. - If so, decode it twice to get back the correct Cyrillic path.
- Redirect the request (301) to that decoded URI. Add a
redirected=1parameter to avoid infinite loops. - Here’s the code:
add_action('init', 'normalize_request_uri_for_ios');
function normalize_request_uri_for_ios() {
$user_agent = $_SERVER['HTTP_USER_AGENT'];
// Check if the user is on an iPhone or iPad
if ((strpos($user_agent, 'iPhone') !== false || strpos($user_agent, 'iPad') !== false)) {
// Only process the URL if it contains '%25', indicating double encoding
if (strpos($_SERVER['REQUEST_URI'], '%25') !== false) {
// Decode the URL twice to fix the double-encoding issue
$decoded_uri = urldecode(urldecode($_SERVER['REQUEST_URI']));
// Prevent infinite redirects by checking if the URL is already decoded
if ($_SERVER['REQUEST_URI'] !== $decoded_uri && !isset($_GET['redirected'])) {
// Add a query parameter to prevent repeated redirects
$new_uri = add_query_arg('redirected', '1', $decoded_uri);
wp_safe_redirect($new_uri, 301);
exit;
}
}
}
}
Step-by-step: How to apply this in your WordPress site
- Open your theme’s functions.php, or better yet, a site-specific plugin.
- Paste the function above.
- Make sure it runs early enough—
initis good. Don’t hook it too late. - Test: send a Cyrillic link (for example
/привет) via Viber → open on iPhone. It should redirect automatically to proper form. - Check also that other functionality isn’t broken (query params, POST requests, etc.).
What to watch out for ?
- Redirect loops: That’s why the
redirected=1flag is important. Without it, you might bounce around. - SSL / domain issues: If you have canonical redirects or HTTPS forced, this redirect should be compatible.
- Caching / rewrites: Some caching layers or URL rewrite rules might conflict. Test with caching off first.
- User-agents spoofed or missing: If for whatever reason
HTTP_USER_AGENTis absent or manipulated, detection might fail. But in most normal cases, it works.