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
- Your app collects card details securely on-device.
- The app sends a JSON message to a WebView using a defined message contract.
- The WebView runs a JS function that uses dLocal’s SDK.
- The SDK returns a success or error response via the WebView bridge.
- 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 he 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. |
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
{
"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)
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" ). |
getBinInformation(args)
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" . |
getInstallmentsPlan(args)
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., "ARG" ). |
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
// 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
- Create and configure a
WebView
with JavaScript enabled. - Add a
JavascriptInterface
to receive JSON strings from the page. - Load the HTML bridge string (or asset file) containing the SDK and
handleMessageFromNative
. - 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.
iOS (Swift)
Requirements
- Xcode 15 or later
- iOS 13+
Typically, no ATS exception is required for https://js.dlocal.com/direct.
Integration steps
- Create a
WKWebView
instance with aWKUserContentController
. - Add a script message handler (e.g., name
iosBridge
) to receive JSON strings. - Load an HTML string that includes the SDK and
handleMessageFromNative
. - 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.
React Native
Requirements
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)
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)
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
- On success from
createToken
, extract the token frompayload.result
. - Send the token to your backend over HTTPS with your order details.
- 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
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] . |
Updated about 16 hours ago