---
updatedAt: 2026-07-27T09:12:27.000Z
agentTools:
  projectIndex: https://docs.dlocal.com/llms.txt
---

# 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.

<br />

***

# 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

| Concept                    | Description                                                                                                                                                                 |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **JavaScript SDK**         | Loaded from `https://js.dlocal.com/direct` inside a minimal HTML page embedded in a WebView.                                                                                |
| **Public key only**        | Use only your dLocal **public key** in the app. Never include secret keys.                                                                                                  |
| **WebView bridge**         | A WebView loads a tiny HTML page that includes the SDK, and the native app sends a JSON message to a WebView; then, the WebView runs the SDK method and returns the result. |
| **Secure collection**      | Card data is collected on-device and sent directly to the SDK. It never touches your backend.                                                                               |
| **Environment separation** | Use the correct public key for the sandbox and production environments. Switch the key at build-time or via configuration.                                                  |
| **Supported SDK actions**  | `createToken`, `getBinInformation`, `getInstallmentsPlan`.  [See more information below](#sdk-operations).                                                                  |

## 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:

| Operation                                             | Description                                                    |
| ----------------------------------------------------- | -------------------------------------------------------------- |
| `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

| Field     | Type   | Required | Description                                                       |
| --------- | ------ | -------- | ----------------------------------------------------------------- |
| `action`  | String | Yes      | One of `createToken`, `getBinInformation`, `getInstallmentsPlan`. |
| `key`     | String | Yes      | Your dLocal public key.                                           |
| `payload` | Object | Optional | Required for `createToken`. Contains card details.                |
| `args`    | Array  | Optional | Used by `getBinInformation` and `getInstallmentsPlan`.            |

#### Example request

```json 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

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

### Request parameters

<br />

##### `createToken(payload)`

| Parameter         | Type   | Required | Description                                                                                            |
| ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------ |
| `name`            | string | Yes      | Cardholder's full name.                                                                                |
| `cvv`             | string | Yes      | Credit card verification value                                                                         |
| `expirationMonth` | string | Yes      | Two digit number representing the card's expiration month (e.g., `"12"`).                              |
| `expirationYear`  | string | Yes      | Number representing the card's expiration year. Accepts two or four digits (e.g., `"30"` or `"2030"`). |
| `pan`             | string | Yes      | Primary account number (card number).                                                                  |
| `country`         | string | Yes      | Payment processing country code. ISO 3166-1 alpha-2 code (e.g., `"AR"`).                               |

<br />

##### `getBinInformation(args)`

Expects: `[bin: string, country: string]`

| Parameter | Type   | Required | Description                                      |
| --------- | ------ | -------- | ------------------------------------------------ |
| `bin`     | string | Yes      | First 6–8 digits of the card number.             |
| `country` | string | Yes      | Country code (ISO 3166-1 alpha-2), e.g., `"AR"`. |

<br />

##### `getInstallmentsPlan(args)`

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

| Parameter  | Type   | Required | Description                                                              |
| ---------- | ------ | -------- | ------------------------------------------------------------------------ |
| `amount`   | number | Yes      | Transaction amount (in the currency specified in the `currency` field).  |
| `currency` | string | Yes      | Three-letter ISO-4217 currency code, in uppercase (e.g., `"ARS"`).       |
| `country`  | string | Yes      | Payment processing country code. ISO 3166-1 alpha-2 code (e.g., `"AR"`). |
| `bin`      | string | Yes      | First 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

```javascript
// 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

<br />

<details>
<summary class="summary_head">

## Android (Kotlin)

</summary>

### Requirements

* Android Studio
* Minimum SDK version 24+
* Internet permission in `AndroidManifest.xml`

```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)

```kotlin
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.

<br />

[See the internal Android guide for the full example >](https://github.com/dlocal/dlocal-direct-js-native-integration/tree/master/android)

</details>

***

<details>
<summary class="summary_head">

## iOS (Swift)

</summary>

### 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)

```swift
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 > ](https://github.com/dlocal/dlocal-direct-js-native-integration/tree/master/ios)

</details>

***

<details>
<summary class="summary_head">

## React Native

</summary>

### Requirements

* [`react-native-webview`](https://github.com/react-native-webview/react-native-webview) installed.

### 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)

```jsx
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)`

<br />

[See the internal React Native guide for the full example >](https://github.com/dlocal/dlocal-direct-js-native-integration/tree/master/react-native)

</details>

***

<br />

> 👍 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](https://github.com/dlocal/dlocal-direct-js-native-integration).

# 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`.

```javascript
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`, which can contain a token or other data depending on the operation.

```json Success response
{
  "type": "success",
  "payload": {
  "result": "..."
  }
}
```

### Error

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

```json Error response
{
  "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

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

## Troubleshooting

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

<br />