Native payment flow

Integrate dLocal Direct in native apps using a secure WebView for card tokenization.

Use a lightweight WebView to embed dLocal Direct JavaScript SDK into native mobile apps.

By embedding the SDK in a lightweight WebView bridge, the SDK performs sensitive operations, such as card tokenization, so you can call the SDK from native code without collecting or transmitting card data to your servers

The same concepts apply to Android (Kotlin), iOS (Swift), and React Native.



How it works

The integration uses a WebView-based bridge to access the dLocal Direct JavaScript SDK from native code. Here's what happens at the technical level:

  • A minimal HTML page loads the dLocal SDK in a WebView.
  • Native code sends a message to the WebView with the desired action (e.g., createToken, getBinInformation).
  • The SDK processes the request and returns the result (success or error) back to the native app.
  • Your app handles the response and forwards the token to your backend for further processing.

Integration overview

  1. Your app collects card details securely on-device.
  2. The app sends a JSON message to a WebView using a defined message contract.
  3. The WebView runs a JS function that uses dLocal’s SDK.
  4. The SDK returns a success or error response via the WebView bridge.
  5. Your app extracts the result and forwards the token to your backend.

Key concepts

ConceptDescription
JavaScript SDKLoaded from https://js.dlocal.com/direct inside a minimal HTML page embedded in a WebView.
Public key onlyUse only your dLocal public key in the app. Never include secret keys.
WebView bridgeA WebView loads a tiny HTML page that includes the SDK, and he native app sends a JSON message to a WebView; then, the WebView runs the SDK method and returns the result.
Secure collectionCard data is collected on-device and sent directly to the SDK. It never touches your backend.
Environment separationUse the correct public key for the sandbox and production environments. Switch the key at build-time or via configuration.
Supported SDK actionscreateToken, getBinInformation, getInstallmentsPlan. See more information below.

Security considerations

  • Never log or store sensitive card data (PANs, CVVs) in the app.
  • Card data must be collected on-device and sent only to the SDK. Only the token should leave the device.
  • Ensure all traffic is sent over HTTPS (TLS). Avoid injecting or loading any third-party content in the WebView.

SDK operations

The dLocal Direct SDK supports the following operations:

OperationDescription
createToken(payload)Tokenizes card details and returns a secure token.
getBinInformation(bin, country)Retrieves BIN (Bank Identification Number) metadata.
getInstallmentsPlan(amount, currency, country, bin)Returns available installment plans based on purchase context.

Message contract

Communication between the native app and the WebView follows a standard JSON message structure.

App to WebView

Example request

FieldTypeRequiredDescription
actionStringYesOne of createToken, getBinInformation, getInstallmentsPlan.
keyStringYesYour dLocal public key.
payloadObjectOptionalRequired for createToken. Contains card details.
argsArrayOptionalUsed by getBinInformation and getInstallmentsPlan.

Example request

{
  "action": "createToken",
  "key": "PUBLIC_KEY",
  "payload": {
    "name": "Jane Doe",
    "cvv": "123",
    "expirationMonth": "12",
    "expirationYear": "30",
    "pan": "4111111111111111",
    "country": "AR"
  }
}

WebView to App

The WebView responds with a JSON string. The native layer should parse this string and handle success or error cases.

Example response

{
  "type": "success",
  "payload": {
    "action": "createToken",
    "result": {
      "card_id": "token_abc123",
      "brand": "VISA",
      "last4": "1111"
    }
  }
}
{
  "type": "error",
  "payload": {
    "message": "Invalid card number",
    "code": "invalid_pan",
    "status": 400
  }
}

Request parameters


createToken(payload)
ParameterTypeRequiredDescription
namestringYesCardholder's full name.
cvvstringYesCredit card verification value
expirationMonthstringYesTwo digit number representing the card's expiration month (e.g., "12").
expirationYearstringYesNumber representing the card's expiration year. Accepts two or four digits (e.g., "30" or "2030").
panstringYesPrimary account number (card number).
countrystringYesPayment processing country code. ISO 3166-1 alpha-2 code (e.g., "AR").

getBinInformation(args)

Expects: [bin: string, country: string]

ParameterTypeRequiredDescription
binstringYesFirst 6–8 digits of the card number.
countrystringYesCountry code (ISO 3166-1 alpha-2), e.g., "AR".

getInstallmentsPlan(args)

Expects: [amount: number, currency: string, country: string, bin: string]

ParameterTypeRequiredDescription
amountnumberYesTransaction amount (in the currency specified in the currency field).
currencystringYesThree-letter ISO-4217 currency code, in uppercase (e.g., "ARG").
countrystringYesPayment processing country code. ISO 3166-1 alpha-2 code (e.g., "AR").
binstringYesFirst 6–8 digits of the card number.

Note: The result payloads are provided by the dLocal SDK and may vary by market/feature enablement

Shared Bridge HTML (WebView)

All platforms use a minimal HTML file that includes the dLocal SDK and a handleMessageFromNative(rawJson) function. This function parses the JSON message, calls the SDK, and returns the result via the platform’s WebView messaging API.

Key logic pseudocode

// Loaded inside WebView
<script src="https://js.dlocal.com/direct"></script>;

async function handleMessageFromNative(raw) {
  const { action, key, payload, args } = JSON.parse(raw);
  const direct = window.dlocal(key);
  let result;
  switch (action) {
    case "createToken":
      result = await direct.createToken(payload);
      break;
    case "getBinInformation":
      result = await direct.getBinInformation.apply(null, args || []);
      break;
    case "getInstallmentsPlan":
      result = await direct.getInstallmentsPlan.apply(null, args || []);
      break;
    default:
      throw new Error("Unknown action: " + action);
  }
  // Post back via platform bridge (Android/iOS/RN differ here)
}

Platform-specific implementation


Android (Kotlin)

Requirements

  • Android Studio
  • Minimum SDK version 24+
  • Internet permission in AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />

Integration steps

  1. Create and configure a WebView with JavaScript enabled.
  2. Add a JavascriptInterface to receive JSON strings from the page.
  3. Load the HTML bridge string (or asset file) containing the SDK and handleMessageFromNative.
  4. Post JSON to the page using evaluateJavascript("window.handleMessageFromNative('<json>')").

Example (abridged)

class MainActivity : ComponentActivity() {
  private lateinit var webView: WebView

  @SuppressLint("SetJavaScriptEnabled")
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    webView = WebView(this)
    setContentView(webView)

    webView.settings.javaScriptEnabled = true
    webView.settings.domStorageEnabled = true
    webView.webViewClient = WebViewClient()

    webView.addJavascriptInterface(object : Any() {
      @JavascriptInterface
      fun postMessage(message: String) {
        android.util.Log.d("dLocalBridge", message) // parse + update UI
      }
    }, "AndroidBridge")

    webView.loadDataWithBaseURL(null, html, "text/html", "utf-8", null)
  }

  private fun postToWeb(message: JSONObject) {
    val js = "window.handleMessageFromNative(" + JSONObject.quote(message.toString()) + ");"
    webView.evaluateJavascript(js, null)
  }

  fun sendCreateToken(publicKey: String, payload: JSONObject) {
    val message = JSONObject().put("action", "createToken").put("key", publicKey).put("payload", payload)
    postToWeb(message)
  }
}

Use sendCreateToken, sendGetBinInformation, and sendGetInstallmentsPlan with your inputs.


See the internal Android guide for the full example >


iOS (Swift)

Requirements

  • Xcode 15 or later
  • iOS 13+

Typically, no ATS exception is required for https://js.dlocal.com/direct.

Integration steps

  1. Create a WKWebView instance with a WKUserContentController.
  2. Add a script message handler (e.g., name iosBridge) to receive JSON strings.
  3. Load an HTML string that includes the SDK and handleMessageFromNative.
  4. Post JSON to the page using evaluateJavaScript("window.handleMessageFromNative(<json>)").

Example (abridged)

class ViewController: UIViewController, WKScriptMessageHandler {
  private var webView: WKWebView!

  override func viewDidLoad() {
    super.viewDidLoad()
    let contentController = WKUserContentController()
    contentController.add(self, name: "iosBridge")

    let config = WKWebViewConfiguration()
    config.userContentController = contentController

    webView = WKWebView(frame: view.bounds, configuration: config)
    view.addSubview(webView)
    webView.loadHTMLString(html, baseURL: nil)
  }

  func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    guard message.name == "iosBridge", let json = message.body as? String else { return }
    print("dLocalBridge:", json) // parse + update UI
  }

  private func postToWeb(json: String) {
    let escaped = json.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\"")
    let js = "window.handleMessageFromNative(" + escaped + ");"
    webView.evaluateJavaScript(js, completionHandler: nil)
  }
}

Use functions like sendCreateToken, sendGetBinInformation, and sendGetInstallmentsPlan to post messages.

See the internal iOS guide for the full example >


React Native

Requirements

Integration options

  • Reuse a hidden WebView as a dedicated bridge component that exposes imperative methods.
  • Load the same minimal HTML and forward messages with window.ReactNativeWebView.postMessage.

Usage example (abridged)

function PaymentsScreen() {
  const bridgeRef = useRef(null);

  function handleBridgeEvent(event) {
    // event: { type: 'success'|'error', payload: {...} }
  }

  function onCreateToken() {
    bridgeRef.current?.createToken({
      name: "JOHN DOE",
      cvv: "123",
      expirationMonth: "12",
      expirationYear: "30",
      pan: "4111111111111111",
      country: "AR",
    });
  }

  return (
    <>
      {/* Your UI here */}
      <DLocalDirectWebView
        ref={bridgeRef}
        publicKey={YOUR_PUBLIC_KEY}
        onEvent={handleBridgeEvent}
      />
    </>
  );
}

The bridge component implements the same message contract defined above and exposes:

  • createToken(payload)
  • getBinInformation(bin, country)
  • getInstallmentsPlan(amount, currency, country, bin)

See the internal React Native guide for the full example >



👍

Full platform-specific examples available

Find complete code implementations for Android, iOS, and React Native in the official integration repository of dLocal Direct JS native integrations.

Handling results and errors

The WebView returns a JSON string that must be parsed in the native layer. Always check the type field to determine whether the operation was successful or unsuccessful.

Example handler (generic):

Always parse the WebView’s JSON string and branch on type.

function handleResult(jsonString) {
  const data = JSON.parse(jsonString);
  if (data.type === "success") {
    // use data.payload.result
  } else {
    // show error from data.payload.message
  }
}

Success

On success, type will be success. The result will be found in payload.result.

{
  "type": "success",
  "payload": {
  "result": { /* token or other data */ }
  }
}

Error

On failure, type will be error. Display a user-friendly message and log diagnostic fields like message, code, status.

{
  "type": "error",
  "payload": {
    "message": "Invalid card number",
    "code": "invalid_pan",
    "status": 400,
    "stack": "...optional stack trace..."
  }
}

Going Live

Going server-side with the token

  1. On success from createToken, extract the token from payload.result.
  2. Send the token to your backend over HTTPS with your order details.
  3. Use your backend’s dLocal server-side APIs to create the charge. Do not attempt to charge from the app.

Pre-launch checklist

  • Public key configured per environment.
  • WebView bridge loads the SDK over HTTPS.
  • createToken returns a token; app sends token to backend.
  • No secrets or raw card data logged or transmitted to merchant servers.
  • Error handling covers type: 'error' with user-friendly messages.

Troubleshooting

IssueSolution
Unknown actionCheck that the action string is valid and supported by the SDK. Ensure correct spelling and casing.
Missing public keyMake sure you're passing the correct dLocal public key in the message to the WebView bridge.
CORS or SDK load errorVerify the WebView can access https://js.dlocal.com/direct. Check for network restrictions or content blockers.
Serialization errorsEnsure all messages sent to/from the WebView are properly stringified with JSON.stringify().
Installments argument mismatchEnsure getInstallmentsPlan uses the correct argument order: [amount:number, currency:string, country:string, bin:string].