> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.coinflow.cash/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.coinflow.cash/_mcp/server.

# How To: Migrate to the Coinflow Fraud SDK

## Overview

The **Coinflow Fraud SDK** replaces the device-signal setup your mobile app does today for [chargeback protection](/guides/checkout/payment-security-risk-management/fraud-protection/implement-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 integration                                                                     | Coinflow Fraud SDK                                     |
| ---------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| **Setup**                    | Install a device module and initialize it with an `appId` and `partnerId` from Coinflow | Initialize once with your `merchantId` and environment |
| **At checkout**              | Read the device ID and attach it, plus any separate session or purchase-token headers   | Attach the headers the SDK returns                     |
| **Headers sent to Coinflow** | `x-device-id` (device ID), plus any other protection headers Coinflow asked you to add  | `x-device-id` only                                     |
| **Header value**             | A device ID                                                                             | An 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.

> **Info**
>
> **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](/guides/checkout/payment-security-risk-management/fraud-protection/implement-chargeback-protection#how-to-add-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

#### Install the Coinflow Fraud SDK

#### iOS (Native)

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

```ruby
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](https://github.com/coinflow-labs-us/coinflow-swift-fraud) for the latest tag.

> **Warning**
>
> 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](https://github.com/coinflow-labs-us/coinflow-swift-fraud/tree/main/Example).

#### Android (Native)

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

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

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

For a working setup, see the [Android example app](https://github.com/coinflow-labs-us/coinflow-android-fraud).

#### React Native

Install the package. React Native autolinks its native module:

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

  ```json
  {
    "expo": {
      "plugins": ["@coinflowlabs/react-native-fraud"]
    }
  }
  ```

* **Bare React Native (iOS):** make the two `Podfile` changes in the [package README](https://github.com/coinflow-labs-us/coinflow-react-native-fraud#native-setup), 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](https://github.com/coinflow-labs-us/coinflow-react-native-fraud#native-setup).

For a working setup, see the [React Native example app](https://github.com/coinflow-labs-us/coinflow-react-native-fraud/tree/main/example).

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

> **Info**
>
> `your-merchant-id` below is an **example value**. Use your own Coinflow `merchantId`.

#### iOS (Native)

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

#### Android (Native)

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

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

#### React Native

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

```tsx
import {CoinflowProtectionProvider} from '@coinflowlabs/react-native-fraud';

export default function App() {
  return (
    <CoinflowProtectionProvider merchantId="your-merchant-id" env="sandbox">
      <RootNavigator />
    </CoinflowProtectionProvider>
  );
}
```

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

#### iOS (Native)

```swift
let fraudHeaders = await CoinflowFraud.shared.purchaseHeaders()
// ["x-device-id": "<opaque value>"]

for (name, value) in fraudHeaders {
  request.setValue(value, forHTTPHeaderField: name)
}
```

#### Android (Native)

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

#### React Native

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

> **Tip**
>
> 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.

```javascript
// 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),
});
```

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

> **Tip**
>
> Coinflow's integrations team can confirm from their side that your sandbox checkouts are arriving with protection signals.

## Troubleshooting

#### The SDK returns no headers

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.

#### Approval rates dropped after migrating

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

#### Do I still need to send chargebackProtectionData?

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](/guides/checkout/payment-security-risk-management/fraud-protection/implement-chargeback-protection).

#### Customers on older app versions

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