Skip to content

Mobi Tech Wise

Learn Mobile APP & AI development techniques in Less Time

Primary Menu
  • Home
  • AI
  • iOS
  • Android
  • Mobile App Security
  • Flutter
  • Automation
  • About Me
Light/Dark Button
  • Home
  • iOS
  • Call Native(iOS/Android) method From WebView JavaScript (using simple JS bridge)
  • Android
  • iOS

Call Native(iOS/Android) method From WebView JavaScript (using simple JS bridge)

Fareeth John September 29, 2025 3 min read

When a page inside your app (e.g., a login form in a WebView) needs to send a custom header with each request, the simplest approach is to expose a tiny native function to JavaScript—then call it just-in-time and attach the value to requests.

This guide shows a modern, safe bridge:

  • Android (Java): Jetpack WebKit addWebMessageListener (origin-restricted) (Android Developers)
  • iOS (Swift): WKScriptMessageHandler with a small promise-style callback

We’ll call the native function getCustomHeaderValue(), and we’ll send it as X-Custom-Header.


Why this pattern?

  • Control: Attach the header on exactly the requests you choose (using fetch()/XHR).
  • Safety: On Android, WebMessageListener lets you allowlist origins and reply via a one-shot reply proxy—safer than exposing a full Java object. (android.googlesource.com)
  • Simplicity: The page just calls a function; native returns a string.

Android (Java) — using addWebMessageListener (recommended)

Add AndroidX WebKit:

dependencies {
  implementation "androidx.webkit:webkit:1.9.0"
}

Activity (Java):

// MainActivity.java (excerpt)
import androidx.webkit.WebViewCompat;
import androidx.webkit.WebViewFeature;
import androidx.webkit.WebMessageCompat;
import androidx.webkit.JavaScriptReplyProxy;

final String JS_OBJECT_NAME = "customHeader";       // window.customHeader
final String ALLOWED_ORIGIN = "https://your-domain.example";

if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
  WebViewCompat.addWebMessageListener(
    webView,
    JS_OBJECT_NAME,
    java.util.Collections.singleton(ALLOWED_ORIGIN),
    (view, message, sourceOrigin, isMainFrame, replyProxy) -> {
      if (!isMainFrame) return;
      if (sourceOrigin == null || !ALLOWED_ORIGIN.equals(sourceOrigin.toString())) return;

      if ("getCustomHeaderValue".equals(message.getData())) {
        String header = CustomHeaderProvider.getCustomHeaderValue(); // your code
        if (header == null) header = "";
        replyProxy.postMessage(WebMessageCompat.create(header));
      }
    }
  );
}

This listener injects a JS object (window.customHeader) that can postMessage(...) to native, and native replies using the JavaScriptReplyProxy. (androidx.de)

Page JS (works with the injected object):

<script>
  function getCustomHeaderValue() {
    return new Promise((resolve) => {
      if (!window.customHeader || typeof window.customHeader.postMessage !== 'function') {
        resolve(""); return;
      }
      // receive the one-shot reply from native:
      window.customHeader.onmessage = (event) => {
        resolve((event && event.data) ? String(event.data) : "");
        window.customHeader.onmessage = null;
      };
      window.customHeader.postMessage('getCustomHeaderValue');
    });
  }

  // Example: attach to login request
  document.getElementById('login-form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const header = await getCustomHeaderValue();
    const body = Object.fromEntries(new FormData(e.target).entries());

    await fetch('/api/login', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Custom-Header': header
      },
      body: JSON.stringify(body),
      credentials: 'include'
    });
  });
</script>

Hardening tips

  • Inject the listener only after verifying the page’s URL matches your allowlist.
  • Disable mixed content; use HTTPS origins. (android.googlesource.com)
  • Keep the native surface tiny: just getCustomHeaderValue().

iOS (Swift) — WKScriptMessageHandler

Create a WKWebView with a message handler named customHeader:

class ViewController: UIViewController, WKScriptMessageHandler {
    private var webView: WKWebView!
    private let handlerName = "customHeader"

    override func viewDidLoad() {
        super.viewDidLoad()
        let cc = WKUserContentController()
        cc.add(self, name: handlerName)
        let config = WKWebViewConfiguration()
        config.userContentController = cc
        webView = WKWebView(frame: view.bounds, configuration: config)
        view.addSubview(webView)
        // load local index.html or your remote page
    }

    private func getCustomHeaderValue() -> String {
        // Your fast, cached implementation
        return "DEMO-CUSTOM-HEADER-VALUE-IOS"
    }

    func userContentController(_ uc: WKUserContentController, didReceive message: WKScriptMessage) {
        guard message.name == handlerName,
              let data = message.body as? String,
              data == "getCustomHeaderValue" else { return }

        let header = getCustomHeaderValue().replacingOccurrences(of: "\"", with: "\\\\\"")
        let js = "window.__resolveCustomHeader && window.__resolveCustomHeader(\"\(header)\");"
        webView.evaluateJavaScript(js, completionHandler: nil)
    }
}

Page JS:

<script>
  function getCustomHeaderValue() {
    return new Promise((resolve) => {
      window.__resolveCustomHeader = (val) => { resolve(val || ""); delete window.__resolveCustomHeader; };
      if (window.webkit?.messageHandlers?.customHeader) {
        window.webkit.messageHandlers.customHeader.postMessage('getCustomHeaderValue');
      } else {
        resolve("");
      }
    });
  }
</script>

Performance & UX

  • Make getCustomHeaderValue() fast. If it can be slow/async, pre-compute and cache in memory; refresh in the background so the bridge returns instantly.
  • If you can’t switch from a traditional <form> submit, inject a hidden field customData and set it before submit; the server can read it from the body.

QA checklist

  • ✅ Header present on all sensitive requests (login, token refresh, etc.).
  • ✅ Empty/invalid header paths tested (server rejects/logs correctly).
  • ✅ Origin allowlist working (injected object absent on untrusted origins).
  • ✅ No mixed content; HTTPS enforced.

Conclusion

By exposing a tiny, purpose-built Custom Header Method (getCustomHeaderValue()) to your WebView and attaching its value with fetch() as X-Custom-Header, you get a clean, testable way to protect critical flows like login without over-exposing native capabilities. On Android, prefer the WebMessageListener approach with an origin allowlist; on iOS, use a minimal WKScriptMessageHandler—both keep the surface area small and the data flow explicit. Pre-warm or cache the header value so calls feel instant, and keep your WebView hardened (HTTPS only, no mixed content). If you must keep traditional form posts, fall back to a hidden field. With this pattern in place, you can extend the same header strategy to any sensitive API call across your app.

Written By
Fareeth John

I’m an Enterprise Architect at Akamai Technologies with over 14 years of experience in mobile app development across iOS, Android, Flutter, and cross-platform frameworks. I’ve built and launched 45+ apps on the App Store and Play Store, working with technologies like AR/VR, OTT, and IoT.

My core strengths include solution architecture, backend integration, cloud computing, CDN, CI/CD, and mobile security, including Frida-based pentesting and vulnerability analysis.

In the AI/ML space, I’ve worked on recommendation systems, NLP, LLM fine-tuning, and RAG-based applications. I’m currently focused on Agentic AI frameworks like LangGraph, LangChain, MCP and multi-agent LLMs to automate tasks

Tags: android webview androidx webkit custom request header fetch api ios wkwebview java android javascript bridge login security Mobile App Security native bridge origin allowlist secure headers Swift webmessagelistener webview wkscriptmessagehandler x-custom-header

Continue Reading

Previous Previous post:

Automating Podcast‑Script Writing with LangGraph

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Related Topics

Fixing “Direct local .aar file dependencies are not supported…” using a Local Maven Repository(Gradle 8)

Fixing “Direct local .aar file dependencies are not supported…” using a Local Maven Repository(Gradle 8)

May 27, 2025
Building a Custom JSON DataStore in SwiftData: A Practical Example

Building a Custom JSON DataStore in SwiftData: A Practical Example

December 3, 2024

Category

  • AI
  • Android
  • Automation
  • Flutter
  • iOS
  • Mobile App Security

Latest Posts

  • Call Native(iOS/Android) method From WebView JavaScript (using simple JS bridge)
  • Automating Podcast‑Script Writing with LangGraph
  • Fixing “Direct local .aar file dependencies are not supported…” using a Local Maven Repository(Gradle 8)
  • Using Frida in an iOS App: A Step-by-Step Guide with an Example Project
  • Building a Custom JSON DataStore in SwiftData: A Practical Example

You may have missed

Call Native(iOS/Android) method From WebView JavaScript (using simple JS bridge)

Call Native(iOS/Android) method From WebView JavaScript (using simple JS bridge)

September 29, 2025
Automating Podcast‑Script Writing with LangGraph

Automating Podcast‑Script Writing with LangGraph

August 1, 2025
Fixing “Direct local .aar file dependencies are not supported…” using a Local Maven Repository(Gradle 8)

Fixing “Direct local .aar file dependencies are not supported…” using a Local Maven Repository(Gradle 8)

May 27, 2025
Using Frida in an iOS App: A Step-by-Step Guide with an Example Project

Using Frida in an iOS App: A Step-by-Step Guide with an Example Project

December 13, 2024
Building a Custom JSON DataStore in SwiftData: A Practical Example

Building a Custom JSON DataStore in SwiftData: A Practical Example

December 3, 2024
Using Frida in Android Apps Through a Simulator: A Step-by-Step Guide

Using Frida in Android Apps Through a Simulator: A Step-by-Step Guide

December 2, 2024
Copyright © All rights reserved. | ChromeNews by AF themes.