Skip to main content
The Mobile Bridge lets the Paylead WebApp (MFP) running inside your native WebView invoke native device capabilities: opening a URL in the system browser, writing to the clipboard, downloading a file. It uses the JSON-RPC 2.0 protocol over postMessage. Your native app receives requests from the WebApp and responds by calling back into JavaScript.
The bridge JavaScript is versioned (v1, v2, …). The machine-readable contract for each version is published as a schema.json. See the Methods reference below for the current version, and the Versions & download page to download a schema or browse older versions.

Transport

WebApp → NativeThe WebApp posts to a message handler; WKWebView auto-deserializes the JSON, so your handler receives a [String: Any] dictionary.
WebApp
window.webkit.messageHandlers.payleadBridge.postMessage(msg)
Native → WebAppCall back with argument binding; never interpolate the JSON into the source.
Native
webView.callAsyncJavaScript(
  "window.payleadBridge.response(jsonString)",
  arguments: ["jsonString": jsonStr],
  ...
)
Never interpolate JSON strings into JavaScript source. Use argument binding (callAsyncJavaScript on iOS) or JSONObject.quote() (Android). Direct interpolation such as "response('${json}')" creates a JS injection vector. This rule applies every time you call back into the WebApp.

Message format

Request (WebApp → Native)

Your handler receives JSON-RPC 2.0 request objects:
{
  "jsonrpc": "2.0",
  "method": "browser.open",
  "params": { "url": "https://example.com" },
  "id": "89d89a45-4f57-4e68-9f61-dc71434b2f26"
}

Success response (Native → WebApp)

Call window.payleadBridge.response(jsonString) with a serialized JSON-RPC 2.0 result:
{ "jsonrpc": "2.0", "result": {}, "id": "89d89a45-4f57-4e68-9f61-dc71434b2f26" }

Error response (Native → WebApp)

If the action fails, respond with a JSON-RPC 2.0 error (see Error codes):
{
  "jsonrpc": "2.0",
  "error": { "code": -32000, "message": "Download failed" },
  "id": "89d89a45-4f57-4e68-9f61-dc71434b2f26"
}

Envelope constraints

  • jsonrpc must equal "2.0". Reject any other value with error code -32600. If the envelope cannot be parsed at all, respond with "id": null and error code -32600.
  • id must be a UUID v4 string. The WebApp always sends a randomly generated UUID v4; non-string ids are silently dropped by the WebApp.
  • The id in the response must match the id from the request.
  • result and error are mutually exclusive. On success include result and omit error; on failure include error and omit result. If the WebApp receives both, error takes precedence and the call rejects.
  • Timeouts. The WebApp rejects requests that don’t receive a response within 5 seconds for browser.open and clipboard.write, and 30 seconds for file.download. For file.download, respond once the download has been triggered (consent granted, transfer started), not once it completes.

Methods

browser.open

Opens a URL in the device’s native browser (outside the WebView).
url
string
required
URL to open. Must use the https:// scheme.Pattern ^https:// · max length 2048
{
  "jsonrpc": "2.0",
  "method": "browser.open",
  "params": {
    "url": "https://www.paylead.fr"
  },
  "id": 1
}
{
  "jsonrpc": "2.0",
  "result": {},
  "id": 1
}

clipboard.write

Copies text to the device clipboard.
text
string
required
Text to copy to clipboard.max length 4096
{
  "jsonrpc": "2.0",
  "method": "clipboard.write",
  "params": {
    "text": "voucher:89d89a45-4f57-4e68-9f61-dc71434b2f26"
  },
  "id": 1
}
{
  "jsonrpc": "2.0",
  "result": {},
  "id": 1
}

file.download

Downloads a file and presents it to the user.
url
string
required
URL of the file to download. Must use the https:// scheme.Pattern ^https:// · max length 200
filename
string
Suggested filename. Allowed characters: letters, digits, underscore, hyphen, dot. Must start with a letter or digit. The native app must still sanitise before writing to disk.Pattern ^[a-zA-Z0-9][a-zA-Z0-9_\-.]*$ · max length 255
mimeType
string
MIME type hint (e.g. application/pdf).one of application/pdf, text/csv, image/jpeg, image/jpg, image/png
{
  "jsonrpc": "2.0",
  "method": "file.download",
  "params": {
    "url": "https://api.paylead.fr/voucher/89d89a45-4f57-4e68-9f61-dc71434b2f26.pdf",
    "filename": "89d89a45-4f57-4e68-9f61-dc71434b2f26.pdf",
    "mimeType": "application/pdf"
  },
  "id": 1
}
{
  "jsonrpc": "2.0",
  "result": {},
  "id": 1
}

Error codes

When a method fails, the native app responds with a JSON-RPC 2.0 error object carrying one of these codes in error.code. error.message must never contain stack traces, filesystem paths, or other sensitive information.
CodeDescription
-32600Invalid JSON-RPC 2.0 request
-32601Method not found
-32602Invalid or missing params
-32000Runtime error (scheme blocked, download failed, etc.) — error.message must not contain stack traces or filesystem paths
-32001Permission denied (location, etc.) — error.message must not contain stack traces or filesystem paths

Integration guide

1

Register the message handler

Register a WKScriptMessageHandler named payleadBridge.
2

Receive the request

Receive JSON-RPC requests in userContentController(_:didReceive:).
3

Execute and respond

Execute the action and call back with callAsyncJavaScript using named argument binding.
let config = WKWebViewConfiguration()
config.userContentController.add(self, name: "payleadBridge")
// Recommended (iOS 14+): restrict navigation to your app's domains
config.limitsNavigationsToAppBoundDomains = true

// Hosts allowed to reach the bridge: any sub-domain of paylead.fr / .tech / .eu
let allowedHost = #"\.paylead\.(fr|tech|eu)$"#

func userContentController(_ controller: WKUserContentController,
                           didReceive message: WKScriptMessage) {
    // Reject messages from sub-frames or unknown origins
    guard message.frameInfo.isMainFrame,
          let host = message.frameInfo.request.url?.host,
          host.range(of: allowedHost, options: .regularExpression) != nil else { return }

    guard message.name == "payleadBridge",
          let body = message.body as? [String: Any],
          let jsonrpc = body["jsonrpc"] as? String, jsonrpc == "2.0",
          let method = body["method"] as? String,
          let id = body["id"] as? String else { return }

    let params = body["params"] as? [String: Any] ?? [:]
    // Validate params against the method's JSON Schema (schema.json).
    // On failure respond with error code -32602 (Invalid params).
    guard validateParams(method: method, params: params) else { return }

    handleBridgeMethod(method, params: params) { result in
        let response: [String: Any] = ["jsonrpc": "2.0", "result": result, "id": id]
        guard let data = try? JSONSerialization.data(withJSONObject: response),
              let jsonStr = String(data: data, encoding: .utf8) else { return }
        // Safe: jsonStr is passed as a named argument, no interpolation into JS
        webView.callAsyncJavaScript(
            "window.payleadBridge.response(jsonString)",
            arguments: ["jsonString": jsonStr],
            in: nil, in: .defaultClient, completionHandler: nil
        )
    }
}
  • Check message.frameInfo.isMainFrame and verify the host against your allowlist before processing any message. Cross-origin iframes inside the WebView otherwise share access to window.webkit.messageHandlers.payleadBridge.
  • Use limitsNavigationsToAppBoundDomains = true and declare your domains under WKAppBoundDomains in Info.plist (iOS 14+).
  • Block navigations to hosts outside your allowlist in decidePolicyFor.

Security checklist for partners

Before shipping your integration, verify each item:
  • HTTPS only: every URL received from the bridge is validated to start with https:// before opening or downloading. javascript:, data:, file:, and intent:// schemes are explicitly rejected.
  • Filename sanitisation: the schema restricts filenames to a safe character set (^[a-zA-Z0-9][a-zA-Z0-9_\-.]*$); as defense in depth, the native app re-validates and strips null bytes / OS-reserved names before writing to disk.
  • Origin allowlist: the bridge is only reachable from pages whose host matches your trusted allowlist (*.paylead.fr, *.paylead.tech, *.paylead.eu). Navigations outside the allowlist are blocked.
  • User consent: file.download triggers a native prompt so the user can confirm or cancel.
  • App-bound domains (iOS 14+): limitsNavigationsToAppBoundDomains = true and WKAppBoundDomains are configured in Info.plist.

General recommendations

  • Back/forward gestures: disable allowsBackForwardNavigationGestures (iOS) and handle the back button explicitly (Android) to avoid accidental off-domain navigations.
  • Loading indicators: file.download can take up to 30 seconds; surface progress in your native UI.
  • Correlation logging: when logging bridge errors natively, include the request id so failures can be cross-referenced with the WebApp’s client-side logs.

What’s next

Page URLs

The WebApp URLs your app can open to land on a specific screen.

Troubleshooting

Diagnose a recurring auth screen, lost cookies, or hard-to-trace behaviour.