Skip to navigation

How To: Migrate to the Coinflow Fraud SDK

Move a mobile app's chargeback protection setup to the Coinflow Fraud SDK, which sends every protection signal in a single header.

Overview

The Coinflow Fraud SDK replaces the device-signal setup your mobile app does today for chargeback protection. Instead of installing and initializing a device module with credentials, reading a device ID, and attaching several protection headers, your app:

  1. Initializes the Coinflow Fraud SDK once with your merchantId.
  2. Asks it for headers at checkout time.
  3. Sends the single x-device-id header it returns on your Coinflow API request.

The SDK collects every device, session and purchase signal Coinflow uses to score a payment and packs them into that one header. You don’t configure individual signal sources, and you don’t need credentials from Coinflow to initialize it.

What changes for your integration

Current integrationCoinflow Fraud SDK
SetupInstall a device module and initialize it with an appId and partnerId from CoinflowInitialize once with your merchantId and environment
At checkoutRead the device ID and attach it, plus any separate session or purchase-token headersAttach the headers the SDK returns
Headers sent to Coinflowx-device-id (device ID), plus any other protection headers Coinflow asked you to addx-device-id only
Header valueA device IDAn opaque value; forward it unchanged

Who should migrate

This guide is for iOS, Android and React Native apps that call Coinflow’s APIs directly, either from the app or through your own backend.

No change is needed for:

  • Web integrations using <CoinflowPurchaseProtection> or the protection script on your site.
  • Coinflow’s hosted checkout (<CoinflowPurchase>), which collects its own protection signals. React Native apps that render <CoinflowPurchase> keep passing deviceId from the existing device module; the Coinflow Fraud SDK’s header isn’t supported as that prop.

Keep following Implement Chargeback Protection for those.

Backward compatibility

Your current integration keeps working, and there is no cutoff date. Coinflow’s API accepts both the existing headers and the Coinflow Fraud SDK’s header on the same endpoints, so:

  • You can migrate one app at a time, in a normal app release.
  • Customers still on an older version of your app keep being protected after you ship the migrated version.
  • No API, endpoint or request-body changes are required; only your protection headers change.

Prerequisites

  • Chargeback protection enabled for your merchant account. Contact Coinflow’s integrations team if it isn’t.
  • Your Coinflow merchantId.
  • iOS: iOS 15 or later, built with Xcode 26 or later, and CocoaPods.
  • Android: minSdk 24 or later, built with Gradle on JDK 17 or later.
  • React Native: a development build or bare React Native app, targeting iOS 15.1 or later. The SDK includes a native module, so it doesn’t run in Expo Go.

Migrate your app

1

Install the Coinflow Fraud SDK

Add the SDK to your Podfile, then run pod install:

platform :ios, '15.0'
target 'YourApp' do
use_frameworks!
pod 'CoinflowFraud', :git => 'https://github.com/coinflow-labs-us/coinflow-swift-fraud.git', :tag => 'v0.2.0'
end

The SDK isn’t on CocoaPods trunk, so the pod line points at its Git repository. See the iOS SDK README for the latest tag.

Install with CocoaPods. The Swift Package Manager build contains the SDK core only and produces no protection signals.

For a working setup, see the iOS example app.

Some of the SDK’s signal sources are served outside Maven Central. Apply Coinflow’s settings plugin, which adds their repositories, in settings.gradle.kts:

pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("cash.coinflow.fraud.repositories") version "0.1.1"
}

The plugin is published to Maven Central, so keep mavenCentral() in pluginManagement. It works whether your app declares repositories in settings.gradle.kts or in a root allprojects block.

Then add the SDK to your app module:

dependencies {
implementation("cash.coinflow:coinflow-fraud:0.1.1")
}

For a working setup, see the Android example app.

Install the package. React Native autolinks its native module:

npm install @coinflowlabs/react-native-fraud
  • Expo: add the SDK’s config plugin to your app config, then rebuild. It adds the iOS SDK to your Podfile, applies the iOS deployment-target fix the SDK needs, and adds the SDK’s Maven repositories to your Android build.

    {
    "expo": {
    "plugins": ["@coinflowlabs/react-native-fraud"]
    }
    }
  • Bare React Native (iOS): make the two Podfile changes in the package README, an extra pod line and a post_install deployment-target fix, then run pod install.

  • Android: usually no extra setup. If your app centralizes repositories in settings.gradle (dependencyResolutionManagement with repositoriesMode), add Coinflow’s cash.coinflow.fraud.repositories settings plugin to android/settings.gradle, as shown in the package README.

For a working setup, see the React Native example app.

2

Initialize the SDK once

Initialize as early as possible, at app launch or when the customer starts shopping, so signals are ready by checkout. Pass your merchantId and the environment that matches the Coinflow API you call: sandbox for api-sandbox.coinflow.cash, prod for production.

your-merchant-id below is an example value. Use your own Coinflow merchantId.
import CoinflowFraud
Task {
await CoinflowFraud.shared.initialize(merchantId: "your-merchant-id", env: .sandbox)
}

initialize never throws, and calling it again has no effect. To switch merchant or environment at runtime, call it with force: true.

Call initialize from your first Activity, passing the Activity itself so the SDK sees the screen that’s already showing:

import cash.coinflow.fraud.CoinflowFraud
import cash.coinflow.fraud.CoinflowFraudEnv
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
CoinflowFraud.initialize(this@MainActivity, "your-merchant-id", CoinflowFraudEnv.SANDBOX)
}
}
}

initialize is a suspend function. It never throws, and calling it again has no effect. To switch merchant or environment at runtime, call it with force = true.

Wrap your app root in CoinflowProtectionProvider. It initializes the SDK once on mount, and re-initializes it if merchantId or env changes.

import {CoinflowProtectionProvider} from '@coinflowlabs/react-native-fraud';
export default function App() {
return (
<CoinflowProtectionProvider merchantId="your-merchant-id" env="sandbox">
<RootNavigator />
</CoinflowProtectionProvider>
);
}
3

Attach the header at checkout

Right before each checkout, payment intent, redeem or subscription request, get the headers from the SDK and add them to the request.

let fraudHeaders = await CoinflowFraud.shared.purchaseHeaders()
// ["x-device-id": "<opaque value>"]
for (name, value) in fraudHeaders {
request.setValue(value, forHTTPHeaderField: name)
}
val fraudHeaders = CoinflowFraud.purchaseHeaders()
// mapOf("x-device-id" to "<opaque value>")
val request = Request.Builder()
.url(checkoutUrl)
.apply { fraudHeaders.forEach { (name, value) -> header(name, value) } }
.post(checkoutBody)
.build()

purchaseHeaders() is a suspend function. The example uses OkHttp; any HTTP client works the same way.

import {useCoinflowFraudHeaders} from '@coinflowlabs/react-native-fraud';
function Checkout() {
const getFraudHeaders = useCoinflowFraudHeaders();
async function pay() {
const response = await fetch(checkoutUrl, {
method: 'POST',
headers: {
...(await getFraudHeaders()), // {'x-device-id': '<opaque value>'}
'content-type': 'application/json',
},
body: JSON.stringify(checkoutBody),
});
}
}

Outside a component, call CoinflowFraud.getPurchaseHeaders() instead.

Call the SDK at checkout time, not at app launch. Some signals are tied to the purchase and are only valid when fetched shortly before the request.

If your backend calls Coinflow rather than the app, send the header value to your backend and forward it to Coinflow as x-device-id, unchanged. Treat it as opaque:

  • Don’t parse, re-encode, trim or log-and-rebuild it.
  • Allow header values up to 8 KB.
  • Also forward the customer’s IP address in x-coinflow-client-ip. Otherwise Coinflow scores the purchase against your server’s IP.
// Your backend, forwarding the value your app sent
await fetch(`https://api-sandbox.coinflow.cash/api/checkout/card/${merchantId}`, {
method: 'POST',
headers: {
'x-device-id': deviceHeaderFromApp,
'x-coinflow-client-ip': customerIp, // the customer's IP, not your server's
'x-coinflow-auth-session-key': sessionKey,
'content-type': 'application/json',
},
body: JSON.stringify(checkoutBody),
});
4

Remove the previous setup

Once the new header is in place, remove from your app:

  • The device module and its initialization with the appId and partnerId Coinflow supplied.
  • Calls that read the device ID (getDeviceId or deviceId).
  • Any other protection headers besides x-device-id that Coinflow asked you to send.

If your app still sends the other protection headers alongside the new x-device-id, the values in x-device-id take priority, so leftover headers won’t break checkout. Removing them keeps your integration simple.

Test your migration

  1. Initialize the SDK with env: sandbox and point your app at https://api-sandbox.coinflow.cash.
  2. Run a sandbox checkout and confirm the request to Coinflow includes a non-empty x-device-id header.
  3. Confirm the checkout response matches what you saw before migrating.
  4. Before releasing, switch to env: prod together with your production API base URL.
Coinflow’s integrations team can confirm from their side that your sandbox checkouts are arriving with protection signals.

Troubleshooting

The SDK fails open: if no signal is available it returns an empty set of headers instead of throwing, so checkout is never blocked. The request still goes through, but without protection signals, which can lower approval rates. Check that:

  • initialize (or CoinflowProtectionProvider) runs before checkout.
  • The merchantId is correct.
  • The app is a development or release build, not Expo Go.
  • On iOS, the CoinflowFraud pod is in your Podfile (the Expo config plugin adds it for you) and you ran pod install or rebuilt.
  • On Android, initialize received an Activity, and, if your app needs it, the cash.coinflow.fraud.repositories plugin is applied.

Coinflow doesn’t reject a checkout because of the x-device-id header. If the value can’t be read, the checkout goes through without its protection signals, which shows up as weaker scoring and more declines rather than an error. Check that:

  • The SDK environment matches the API you call: sandbox with api-sandbox.coinflow.cash, prod with production.
  • Your backend forwards x-device-id exactly as the app sent it, along with the customer’s IP in x-coinflow-client-ip.

Yes. The Coinflow Fraud SDK replaces how device and session signals are collected, not what you send about the purchase. Keep passing chargebackProtectionData as described in Implement Chargeback Protection.

No action is needed. Requests from older app versions that still send the previous headers keep being accepted and scored.