Coinme Risk SDK for React Native

Real-time fraud detection and risk assessment for React Native apps; a thin native-module bridge over Coinme's production iOS and Android risk SDKs, exposed as a typed JS/TS API.

Package: @coinme-security/risk-sdk-react-native Version: 1.0.1 License: Proprietary — Coinme, Inc.

What this SDK is

@coinme-security/risk-sdk-react-native is the React Native binding for the Coinme Risk Engine. It wraps Coinme's native iOS (risk-sdk-ios) and Android (risk-sdk-android) SDKs via a thin React Native native-module bridge and exposes a single typed JS/TS API that matches its sibling SDKs.

One npm package. One native module name. One consumer-facing API. If you're already using @coinme-security/risk-sdk-js on web or the native iOS / Android SDKs, this is the RN port of the same conceptual model:

setup → get session tag → update → submit data before each transaction-initiating server call

What it collects

The native engine collects a layered set of signals from the user's session:

  • Behavioral biometrics — typing cadence, interaction timing, touch patterns
  • Sensor data — motion, orientation, device pressure
  • Device context — clipboard activity, focus patterns, screen engagement
  • Device fingerprinting — device identifiers, network state, SIM info (Android), location (with user consent)
  • Field interactions — text changes and focus events on tracked inputs

Those signals are flushed to Coinme's Risk Engine when you call submitCoinmeRiskData(). Coinme's transaction services consume the signals internally when evaluating the transactions your app initiates.

Why does Risk SDK exist?

Coinme uses real-time behavioral risk signals as part of how we evaluate whether to process or reject a transaction. The Risk SDK is the mechanism that collects those signals on-device and correlates them — via a device fingerprint you control — to the server calls they belong to.

⚠️

Integration is required.

Partners cannot use Coinme's transactional services without integrating the Risk SDK on every surface that initiates a transaction. Transactions unsupported by risk metrics will be rejected outright.

Your responsibilities as an integrator:

  • Generate and persist a stable device fingerprint (see section 9)
  • Call submitCoinmeRiskData() before every transaction-initiating server request (see section 12)
  • Handle privacy policy disclosures and store-listing compliance (see section 14)

How does the Risk SDK work?

┌──────────────────────────────────────────────────────────────┐
│  Your React Native app                                       │
│                                                              │
│  ┌────────────────────────────────────────────────────┐      │
│  │  @coinme-security/risk-sdk-react-native (JS/TS)    │      │
│  │  setupCoinmeRiskEngine / updateCoinmeRiskEngine    │      │
│  │  getSessionTag / getPartnerSessionTag              │      │
│  │  submitCoinmeRiskData / CoinmeTrackedTextInput     │      │
│  └───────────────────────┬────────────────────────────┘      │
│                          │ RN bridge                         │
│  ┌───────────────────────┴────────────────────────────┐      │
│  │  Native module: CoinmeRiskEngine                   │      │
│  │  ├── iOS:     risk-sdk-ios (xcframework)           │      │
│  │  └── Android: risk-sdk-android (aar)               │      │
│  └────────────────────┬───────────────────────────────┘      │
└───────────────────────┼──────────────────────────────────────┘
                        │
                        ▼
             Coinme Risk Engine  ◀── session tag + signals
                        │
                        ▼
          Consumed by Coinme's transaction
          services during evaluation of
          customer transaction requests

The JS layer forwards typed calls across the bridge to the native engine, which does the actual signal collection. The public API mirrors CoinmeRiskEngine on iOS and Android, so integration code looks the same across all three platforms.

The signals are consumed by Coinme's internal transaction services, not returned to partners. The SDK submits behavioral, device, and interaction data to the Risk Engine, which Coinme uses as part of its decision to process or reject a transaction. Risk scores and the underlying signals are not exposed to partner backends.

Compatibility

RequirementSupported
Node20.9+
React18+
React Native0.72 – 0.81 (see note below)
iOS16.0+ (required by the underlying CoinmeRiskSDK xcframework)
AndroidminSdkVersion 24+ (Android 7.0 Nougat)
Expo✅ SDK 51+ (managed workflow with expo prebuild) — see Expo support
⚠️

React Native 0.82+ is not yet supported.

The bridge targets RN's legacy (bridge) architecture; TurboModule support is planned but has not landed. RN 0.82 defaults the New Architecture ON and removes the off-switch. Pin to 0.81 or older until TurboModule support ships.

Obtaining access

The Risk SDK is distributed privately. You need a Coinme Cloudsmith entitlement token — a single credential that authenticates three things:

  1. The private npm registry at npm.coinme.com (for the JS package)
  2. The CocoaPods repository (for the iOS native artifact)
  3. The Maven repository (for the Android native artifact)

One token covers all three. If you don't already have one, contact your Coinme integration representative or the Coinme business development team — this is not a self-serve credential.

Throughout this guide, the token is referred to as CLOUDSMITH_TEAM_TOKEN. Export it as an environment variable before any build step:

export CLOUDSMITH_TEAM_TOKEN=<your-token>

For persistence across shells, also add it to:

  • ~/.gradle/gradle.properties (covers Gradle)
  • Your shell rc file (~/.zshrc, ~/.bashrc, etc. — covers CocoaPods)

Do not commit the token to version control. Use CI secrets for automated builds.

Installation

1. Install the npm package

The package is hosted on Coinme's private npm registry. Configure the scope before installing.

For npm / Yarn Classic, add to .npmrc at your repo root:

@coinme-security:registry=https://npm.coinme.com/coinme-security-wrapper/
//npm.coinme.com/coinme-security-wrapper/:_authToken=${CLOUDSMITH_TEAM_TOKEN}
always-auth=true

For Yarn Berry (v2+), add to .yarnrc.yml:

nodeLinker: node-modules

npmScopes:
  coinme-security:
    npmRegistryServer: 'https://npm.coinme.com/coinme-security-wrapper/'
    npmAuthToken: '${CLOUDSMITH_TEAM_TOKEN}'

Then install:

export CLOUDSMITH_TEAM_TOKEN=<your-token>
yarn add @coinme-security/risk-sdk-react-native
# or
npm install @coinme-security/risk-sdk-react-native

Autolinking will discover this package automatically — you do not need to register the native module by hand on either platform.

2. iOS setup

Three things to do in ios/Podfile:

  1. Add the Cloudsmith and CocoaPods CDN sources at the very top of the file. Use double quotes so Ruby interpolates the env var:
source "https://dl.cloudsmith.io/#{ENV['CLOUDSMITH_TEAM_TOKEN']}/coinme/coinme-sdk-mobile/cocoapods/index.git"
source "https://cdn.cocoapods.org/"
  1. Set the iOS deployment target. CoinmeRiskSDK requires iOS 16+ and RN's default is lower:
platform :ios, '16.0'
  1. Disable the New Architecture by passing new_arch_enabled: false to use_react_native! (or set RCT_NEW_ARCH_ENABLED=0 at install time).

Inside the main target '<AppName>' do block, confirm use_frameworks! :linkage => :static is present (most modern RN templates include this).

Then install:

export CLOUDSMITH_TEAM_TOKEN=<your-token>
cd ios && pod install

Autolinking picks up the package's podspec automatically. You do not add a pod '...' line yourself.

3. Android setup

Step 1 — Add Coinme's Cloudsmith Maven repo.

In android/settings.gradle (or .kts), add a dependencyResolutionManagement block. Read the token from the environment rather than committing it:

def cloudsmithToken = System.getenv('CLOUDSMITH_TEAM_TOKEN') ?: ''

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
    repositories {
        google()
        mavenCentral()
        maven {
            name = "CoinmeSDK"
            url = uri("https://dl.cloudsmith.io/${cloudsmithToken}/coinme/coinme-sdk-mobile/maven/")
        }
    }
}

If your template uses allprojects { repositories { … } } in the root build.gradle instead of dependencyResolutionManagement (common on RN 0.72–0.75), add the same Maven block there.

Step 2 — Force Kotlin 2.3.0.

risk-sdk-android is compiled with Kotlin 2.3.0. React Native's Gradle plugin pins an older version, which causes this build failure:

Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.3.0, expected version is 2.1.x.

In android/build.gradle, set kotlinVersion to 2.3.0, pin the plugin explicitly, and add resolution strategies in both buildscript and allprojects:

buildscript {
    ext {
        buildToolsVersion = "35.0.0"
        minSdkVersion = 24
        compileSdkVersion = 35
        targetSdkVersion = 35
        ndkVersion = "27.1.12297006"
        kotlinVersion = "2.3.0"   // ← must be 2.3.0
    }
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath("com.android.tools.build:gradle")
        classpath("com.facebook.react:react-native-gradle-plugin")
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
    }
    // Force Kotlin 2.3.0 for classpath dependencies
    configurations.classpath {
        resolutionStrategy.eachDependency { details ->
            if (details.requested.group == 'org.jetbrains.kotlin') {
                details.useVersion kotlinVersion
            }
        }
    }
}

// Force Kotlin stdlib 2.3.0 for all subprojects
allprojects {
    configurations.all {
        resolutionStrategy.eachDependency { details ->
            if (details.requested.group == 'org.jetbrains.kotlin' &&
                details.requested.name.startsWith('kotlin-stdlib')) {
                details.useVersion rootProject.ext.kotlinVersion
            }
        }
    }
}

apply plugin: "com.facebook.react.rootproject"
⚠️

Important:

allprojects must be at the root level of build.gradle, not nested inside buildscript.

Step 3 — Disable the New Architecture.

In android/gradle.properties:

newArchEnabled=false

Step 4 — Build.

export CLOUDSMITH_TEAM_TOKEN=<your-token>
cd android && ./gradlew clean

Autolinking links the module; you do not add an implementation("com.coinme:...") line yourself.

Platform configuration

1. Android permissions

Add these to AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />

Usage reference:

PermissionUsage
INTERNETNetwork communication for fraud detection
ACCESS_COARSE_LOCATION / ACCESS_FINE_LOCATIONLocation signals for fraud detection
ACCESS_WIFI_STATEWiFi name and status
ACCESS_NETWORK_STATENetwork information and state
READ_PHONE_STATEPhone/network info (MNC, MCC, IMEI, phone number, phone type, SIM number)
USE_BIOMETRIC / USE_FINGERPRINTBiometric authentication settings (Face / Fingerprint)
WRITE_EXTERNAL_STORAGEWrite data to check re-installation behavior
READ_EXTERNAL_STORAGEExternal storage status, total size, free size
READ_GSERVICESDevice GSF ID (Google Services Framework ID)
⚠️

Important:

Location and phone-state permissions are dangerous permissions on Android 6.0+ and require runtime permission requests. Handle the flow in your app.

2. Android ProGuard / R8

If you use ProGuard or R8 for code obfuscation, add these rules to proguard-rules.pro:

# Keep Google Play Services classes
-keep class com.google.android.gms.** { *; }
-keep class com.google.android.gms.tasks.** { *; }
-keep class com.google.android.gms.ads.identifier.AdvertisingIdClient { *; }

# Keep Sardine SDK classes
-keep class ai.sardine.** { *; }
-keepclassmembers class ai.sardine.** { *; }

3. Google Play Console requirements

Before releasing your app with the Risk SDK:

Privacy Policy — update your Play Store privacy policy with the following disclosure:

For fraud protection purposes only, (1) we collect your Battery Usage, Device Identifier, Device Storage, MAC Address, and SIM information; (2) we also collect enough information to determine if you are trying to fake your current location by using a VPN, VPN apps with location spoofing, or other related tools.

Data Safety declaration — in the Play Console, go to App content → Data safety and declare these collected data types:

  • Location (Approximate and Precise)
  • Device or other IDs (Device ID)
  • App info and performance (Crash logs, Diagnostics)
  • Files and docs (for storage-related fraud detection)

Add any other data types your app collects beyond what the SDK requires. Your integration team may provide a Data Safety CSV you can import directly.

4. iOS Info.plist keys

Location permissions are required. Add to Info.plist:

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>We use your location to verify transactions and prevent fraud.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>We use your location to verify transactions and prevent fraud.</string>

If you also want Face ID signal detection:

<key>NSFaceIDUsageDescription</key>
<string>Face ID is used to verify your identity.</string>

Optional iOS capabilities (added via Xcode → Signing & Capabilities):

  • Access WiFi Information — required for WiFi BSSID/SSID as an additional location signal. Requires location services to be enabled first.
  • iCloud + CloudKit — enables iCloud record ID (consistent across devices on the same Apple account). If you add this capability, you must set setCloudEntitlements: true in the setup options. Do not enable setCloudEntitlements without adding the CloudKit capability — the underlying native SDK will crash.

5. App Store Connect requirements

Before releasing, update App Store Connect → App Privacy:

  1. Data Collection — select "Yes, we collect data from this app"
  2. Location — select Coarse and/or Precise Location. Purpose: App Functionality. Reason: "Required for fraud detection and prevention."
  3. Identifiers → Device ID — Purpose: App Functionality. Check "Yes, device ID is linked to user identity."

Privacy Policy — update your App Store privacy policy with the following disclosure:

For fraud protection purposes only, (1) we collect your Battery Usage, Device Identifier, Device Storage, and location information; (2) we also collect enough information to determine if you are trying to fake your current location by using a VPN or other location-spoofing tools.

Quickstart

The canonical flow:

setupCoinmeRiskEngine getSessionTag (or getPartnerSessionTag) → updateCoinmeRiskEngine with the returned webSessionID submitCoinmeRiskData → your server call.

import {
  getSessionTag,
  setupCoinmeRiskEngine,
  submitCoinmeRiskData,
  updateCoinmeRiskEngine,
} from '@coinme-security/risk-sdk-react-native';

const fingerprint = getOrCreateDeviceFingerprint(); // your choice — see section 9

// 1. Initialise the engine first — before any session-tag call.
await setupCoinmeRiskEngine({
  mode: 'prod',
  clientId: 'your-client-id',
  customerId: 'customer-123',
});

// 2. Fetch the session tag (requires the engine to be initialised).
const { webSessionID } = await getSessionTag({
  apiBaseUrl: 'https://api.coinme.com',
  authToken: userAuthToken,
  fingerprint,
  additionalHeaders: { 'x-partner-id': 'your-partner-id' },
});

// 3. Bind the session key to the running engine.
await updateCoinmeRiskEngine({ sessionKey: webSessionID });

// 4. Flush signals before the transaction-initiating server call.
await submitCoinmeRiskData();

// 5. Make the server call — MUST include the same fingerprint.
await fetch('https://api.coinme.com/transactions', {
  headers: { 'x-device-fingerprint': fingerprint },
  // ...
});

Unauthenticated / partner flows

For pre-login flows or partner embeds where you don't have a user auth token yet, swap getSessionTag for getPartnerSessionTag:

const { webSessionID } = await getPartnerSessionTag({
  partnerId: 'your-partner-id',
  customerId: 'customer-123',
  fingerprint,
  rampId: 'optional-ramp-id',
});
await updateCoinmeRiskEngine({ sessionKey: webSessionID });

getPartnerSessionTag resolves its URL automatically from the mode you passed to setupCoinmeRiskEngine.

The fingerprint contract

⚠️

This is the single most common source of silent integration bugs.

The fingerprint value you pass to getSessionTag / getPartnerSessionTag MUST be byte-identical to the x-device-fingerprint header on every downstream backend call for the same user/session.

Mismatched fingerprints break risk-signal correlation. No error is raised at the SDK layer, but on the server side, Coinme cannot tie frontend signals to backend requests, and the affected transactions will be rejected.

You choose the source

The SDK does not generate fingerprints for you. Common options:

  • react-native-device-infogetUniqueId() is stable per install
  • expo-applicationgetAndroidId() / getIosIdForVendorAsync() (for the future Expo path)
  • A UUID persisted to secure storage on first launch
  • A server-issued device ID returned from your own registration endpoint

The rules, whichever source you pick

  1. Stable across app sessions for the same install
  2. Identical on every surface that touches risk — the fingerprint option passed to getSessionTag / getPartnerSessionTag, and the x-device-fingerprint header on every subsequent BE call in the same session
  3. Opaque to the SDK — we don't validate format, length, or entropy

Example: wrapping your fingerprint source

One way to guarantee consistency is to wrap your fingerprint source in a single module and route everything — SDK calls, fetch interceptors, axios defaults — through that module:

// src/risk/fingerprint.ts
import DeviceInfo from 'react-native-device-info';

let cached: string | null = null;

export const getDeviceFingerprint = async (): Promise<string> => {
  if (cached) return cached;
  cached = await DeviceInfo.getUniqueId();
  return cached;
};

Use that single helper everywhere — both in getSessionTag / getPartnerSessionTag and in your HTTP client's default headers — so the value never drifts.

Lifecycle

The three calls you need to think about:

App start ─▶ setupCoinmeRiskEngine      ◀── no sessionKey needed yet
                    │
                    ▼
             getSessionTag              ◀── or getPartnerSessionTag
             (returns webSessionID)
                    │
                    ▼
          updateCoinmeRiskEngine        ◀── sessionKey = webSessionID
          (also: customerId, flow, …)
                    │
                    ▼
          submitCoinmeRiskData          ◀── before every transaction-initiating call
                    │
                    ▼
             your server call
          (x-device-fingerprint header)
  1. Setup once per app session. sessionKey is optional at setup — the native SDK auto-generates one if omitted. After calling getSessionTag / getPartnerSessionTag, pass the returned webSessionID to updateCoinmeRiskEngine.
  2. Update whenever context changes.
  • User logs in → updateCoinmeRiskEngine({ customerId })
  • User starts a high-risk flow → updateCoinmeRiskEngine({ flow: FlowType.CardTransaction })

Cached config is merged, not replaced. The underlying native SDK auto-submits on update.

  1. Submit before every transaction-initiating server call. Forgetting this produces no error at the SDK layer — signals are collected but never flushed — and the downstream transaction will be rejected by Coinme's services.

On session rotation (logout, account switch), call clearSessionKey() and start the cycle over with a fresh getSessionTag / getPartnerSessionTag.

Tracking user input

<CoinmeTrackedTextInput> — the recommended path

A drop-in replacement for React Native's TextInput. Wires onChangeText / onFocus / onBlur to the native risk engine under a stable riskIdentifier, and forwards every other prop — including your own callbacks and refs — unchanged.

import { useState } from 'react';
import { CoinmeTrackedTextInput } from '@coinme-security/risk-sdk-react-native';

export const LoginForm = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  return (
    <>
      <CoinmeTrackedTextInput
        riskIdentifier="email"
        value={email}
        onChangeText={setEmail}
        autoCapitalize="none"
        keyboardType="email-address"
      />
      <CoinmeTrackedTextInput
        riskIdentifier="password"
        value={password}
        onChangeText={setPassword}
        secureTextEntry
      />
    </>
  );
};

Works in both controlled and uncontrolled mode. Your own onChangeText / onFocus / onBlur are still invoked.

Raw trackers (escape hatches)

For custom components or imperative flows where CoinmeTrackedTextInput doesn't fit:

import {
  trackRiskFocusChange,
  trackRiskTextChange,
} from '@coinme-security/risk-sdk-react-native';

await trackRiskTextChange('coupon_code', value);
await trackRiskFocusChange('coupon_code', true);

Rules:

  • Use the same riskIdentifier for both the text and focus calls on a given field
  • Use descriptive, stable identifiers: "email", "password", "cardNumber" — not "input1"
  • Prefer CoinmeTrackedTextInput when you can — it handles the wiring for you

No page tracking. The iOS and Android SDKs don't expose a page-tracking primitive, and this package matches that. There is no trackRiskPage or useTrackCoinmeRiskPage export.

When to submit data

submitCoinmeRiskData() flushes the in-memory buffer of collected signals to the Risk Engine. It is what makes the signals available to Coinme's transaction services for evaluation.

Call it before every transaction-initiating server call

Examples of calls that require a preceding submitCoinmeRiskData():

  • Payment and card charge endpoints
  • Wallet transfers and crypto transactions
  • Onboarding and KYC submissions
  • Adding or updating payment methods
  • Money transfers and withdrawals

If you are unsure whether a given endpoint requires risk signals, assume it does. Transactions that reach Coinme's services without correlated risk data will be rejected.

What gets submitted

  • Behavioral biometrics (typing patterns, interaction timing)
  • Sensor data (motion, orientation, device position)
  • Device context (clipboard, focus patterns, screen time)
  • All tracked field interactions

Operational notes

  • Safe to call multiple times — the latest collected data is sent each time
  • Always await it before the server call it precedes
  • Submit failures are not automatically retried — if the call rejects, the subsequent transaction will not have correlated signals and will be rejected by Coinme's services
try {
  await submitCoinmeRiskData();
  await processPayment();
} catch (err) {
  console.error('Risk subtumit failed', err);
  // The subsequent transaction will not have correlated signals — do not proceed.
}

API reference

All exports are named. There are no default exports.

Functions

ExportDescription
setupCoinmeRiskEngineInitialize the native Risk Engine. sessionKey is optional — omit at setup, then pass the webSessionID from the session-tag call via updateCoinmeRiskEngine.
updateCoinmeRiskEngineMerge config updates (customerId, flow, sessionKey, …) into the cached session. Throws if called before setup.
getCoinmeRiskEngineConfigReturns the locally cached config. Never round-trips to native.
clearSessionKeyClears the in-memory session key. Use on logout / account switch.
submitCoinmeRiskDataFlushes collected signals to the Risk Engine. Call before every transaction-initiating server request.
getSessionTagCalls Coinme's authenticated /get-session-tag endpoint. Caches the returned webSessionID as the active session key.
getPartnerSessionTagCalls Coinme's public partner session-tag endpoint. URL is auto-resolved from mode.
trackRiskTextChangeImperative text-change tracker.
trackRiskFocusChangeImperative focus / blur tracker.

Components

ExportDescription
CoinmeTrackedTextInputTextInput wrapper that tracks change / focus / blur by riskIdentifier. Forwards all other props unchanged.

Enums and types

TypeDescription
FlowTypeEnum — Onboarding, CardTransaction, CardLinking.
Mode`'test''prod'`.
AppRegion`'default''eu''ca''in''au'`. Mirrors the native SDKs.
RiskEngineSetupOptionsOptions for setupCoinmeRiskEngine: mode, clientId, sessionKey?, flow?, customerId?, partnerId?, region?, enableBehaviorBiometrics?, enableClipboardTracking?, enableFieldTracking?.
RiskEngineUpdateOptionsPartial update shape: sessionKey?, flow?, customerId?, …
RiskEngineConfigShape of the cached config returned from getCoinmeRiskEngineConfig.
GetSessionTagOptionsOptions for getSessionTag: apiBaseUrl, authToken, fingerprint, timeout?, additionalHeaders?.
GetPartnerSessionTagOptionsOptions for getPartnerSessionTag: partnerId, customerId, fingerprint, rampId?, timeout?, additionalHeaders?.
SessionTagData{ webSessionID: string; orgID?: string }.
CoinmeTrackedTextInputPropsExtends React Native's TextInputProps with a required riskIdentifier: string.

Setup options in detail

OptionTypeRequiredDefaultDescription
modeModeYes'test' or 'prod'
clientIdstringYesYour Coinme-issued client ID
partnerIdstringNoPartner identifier (distinct from clientId)
customerIdstringNoCustomer / user identifier
sessionKeystringNoautoSession key — auto-generated if omitted
flowFlowTypeNoOnboarding, CardTransaction, or CardLinking
regionAppRegionNodefaultdefault (US), eu, ca, au, in
enableBehaviorBiometricsbooleanNotrueEnable behavioral biometrics collection
enableClipboardTrackingbooleanNofalseEnable clipboard activity tracking
enableFieldTrackingbooleanNotrueEnable automatic field tracking

clientId and partnerId are different values. Do not use them interchangeably.

Platform parity

This package's public API mirrors the CoinmeRiskEngine surface of risk-sdk-ios and risk-sdk-android — the same lifecycle, the same option names, the same two session-tag variants. If you maintain a native iOS or Android app alongside an RN app, your integration code will look the same in all three.

Privacy & compliance

User consent for behavior biometrics

If enableBehaviorBiometrics is true (the default), ensure users have agreed to your privacy policy before collecting any biometric data. This is required for GDPR, CCPA, and equivalent privacy regulations.

One way to handle this is to defer setupCoinmeRiskEngine until consent is recorded on first launch, or to initialise with enableBehaviorBiometrics: false and flip it later via updateCoinmeRiskEngine. The right approach depends on your app's consent flow — check with your legal and compliance teams.

Privacy policy disclosures

Update your app's privacy policy to disclose data collection for fraud protection. The required disclosure text is in section 7.3 (Android) and section 7.5 (iOS).

Store-listing requirements

  • Google Play — declare data types in the Data Safety section (see section 7.3)
  • App Store — declare data types in App Privacy (see section 7.5)

Your integration team may provide pre-filled CSV imports for both.

Expo support

Step-by-step instructions for adding @coinme-security/risk-sdk-react-native to an existing Expo app (managed workflow with expo prebuild).

Prerequisites

  • Expo SDK 51+
  • iOS deployment target 16.0+ (set automatically by the plugin)
  • Android minSdkVersion 24+
  • CLOUDSMITH_TEAM_TOKEN — the Cloudsmith entitlement token provided by Coinme during onboarding. Contact your Coinme integration representative if you don't have it.

Before you begin — export your token

Export CLOUDSMITH_TEAM_TOKEN once at the start of your terminal session. All subsequent commands in this guide (package install, pod install, Gradle build) will pick it up automatically.

export CLOUDSMITH_TEAM_TOKEN=<your-token>

1. Install the JS package

The package is hosted on a private Cloudsmith npm registry. Configure the scope before installing by adding the registry to your project's config file at the repo root.

.npmrc (npm / Yarn Classic):

@coinme-security:registry=https://npm.coinme.com/coinme-security-wrapper/
//npm.coinme.com/coinme-security-wrapper/:_authToken=${CLOUDSMITH_TEAM_TOKEN}

.yarnrc.yml (Yarn Berry v2+):

nodeLinker: node-modules

npmScopes:
  coinme-security:
    npmRegistryServer: 'https://npm.coinme.com/coinme-security-wrapper/'
    npmAuthToken: '${CLOUDSMITH_TEAM_TOKEN}'

Then install:

yarn add @coinme-security/risk-sdk-react-native

2. Add the config plugin

Add the plugin to your Expo config. No options are required — native credentials are resolved from CLOUDSMITH_TEAM_TOKEN at build time, not stored in the config.

app.json:

{
  "expo": {
    "plugins": [
      "@coinme-security/risk-sdk-react-native"
    ]
  }
}

app.config.js (if you use a dynamic config):

export default {
  // ...
  plugins: [
    '@coinme-security/risk-sdk-react-native',
  ],
};

3. Build and run

The plugin applies all native changes automatically during prebuild. You do not need to manually edit any native files.

PlatformWhat the plugin configures
iOS PodfileAdds Cloudsmith CocoaPods source (token read at pod install time via ENV.fetch)
iOS PodfileSets deployment target to 16.0
Android build.gradleAdds Cloudsmith Maven repository (token read at build time via System.getenv)
Android build.gradleForces Kotlin 2.3.0 and adds resolution strategies

iOS:

npx expo run:ios

Android:

npx expo run:android

These commands run expo prebuild (if needed), pod install / Gradle build, and launch on device. Because CLOUDSMITH_TEAM_TOKEN is already exported, no extra steps are required.

To regenerate native directories from scratch, run expo prebuild --clean before npx expo run:ios / npx expo run:android.

4. JS integration

4a. Initialization sequence

The native engine must be initialized before calling getPartnerSessionTag. Use setupCoinmeRiskEngine first, then fetch the session tag and update the engine with it:

import {
  setupCoinmeRiskEngine,
  getPartnerSessionTag,
  updateCoinmeRiskEngine,
  FlowType,
} from '@coinme-security/risk-sdk-react-native';

// Step 1 — initialize the native engine
await setupCoinmeRiskEngine({
  mode: 'test', // 'test' | 'prod'
  clientId: CLIENT_ID,
  partnerId: CAAS_PARTNER_ID,
  customerId: CUSTOMER_ID,
  flow: FlowType.Onboarding,
  enableFieldTracking: true,
  enableBehaviorBiometrics: true,
});

// Step 2 — get the session tag (requires the engine to be initialized)
const tag = await getPartnerSessionTag({
  partnerId: CAAS_PARTNER_ID,
  customerId: CUSTOMER_ID,
  fingerprint: FINGERPRINT,
});

// Step 3 — bind the session key to the running engine
await updateCoinmeRiskEngine({ sessionKey: tag.webSessionID });

CLIENT_ID and CAAS_PARTNER_ID are different values. Do not use them interchangeably.

4b. Tracked input fields

Replace standard TextInput components with CoinmeTrackedTextInput to forward keystrokes and focus events to the native SDK:

import { CoinmeTrackedTextInput } from '@coinme-security/risk-sdk-react-native';

<CoinmeTrackedTextInput
  riskIdentifier="email"
  keyboardType="email-address"
  autoCapitalize="none"
  placeholder="[email protected]"
  style={styles.input}
/>

<CoinmeTrackedTextInput
  riskIdentifier="password"
  secureTextEntry
  placeholder="password"
  style={styles.input}
/>

riskIdentifier is a stable string label that the SDK uses to associate events with a specific field.

5. Complete integration skeleton

Minimal end-to-end example — SDK calls only, no UI boilerplate. Replace the REPLACE_ME_* constants with your real values.

import {
  CoinmeTrackedTextInput,
  FlowType,
  getPartnerSessionTag,
  setupCoinmeRiskEngine,
  updateCoinmeRiskEngine,
} from '@coinme-security/risk-sdk-react-native';

const CLIENT_ID = 'REPLACE_ME_CLIENT_ID';
const CAAS_PARTNER_ID = 'REPLACE_ME_CAAS_PARTNER_ID'; // distinct from CLIENT_ID
const CUSTOMER_ID = 'REPLACE_ME_CUSTOMER_ID';
const FINGERPRINT = 'REPLACE_ME_FINGERPRINT';

// Step 1 — initialize the native engine
const handleInit = async () => {
  await setupCoinmeRiskEngine({
    mode: 'test', // 'test' | 'prod'
    clientId: CLIENT_ID,
    partnerId: CAAS_PARTNER_ID,
    customerId: CUSTOMER_ID,
    flow: FlowType.Onboarding,
    enableFieldTracking: true,
    enableBehaviorBiometrics: true,
  });
};

// Step 2 — fetch session tag and bind it to the engine
const handleGetSessionTag = async () => {
  const tag = await getPartnerSessionTag({
    partnerId: CAAS_PARTNER_ID,
    customerId: CUSTOMER_ID,
    fingerprint: FINGERPRINT,
  });
  await updateCoinmeRiskEngine({ sessionKey: tag.webSessionID });
};

// Step 3 — tracked input fields (call after step 2)
const TrackedForm = () => (
  <>
    <CoinmeTrackedTextInput
      riskIdentifier="email"
      keyboardType="email-address"
      autoCapitalize="none"
      placeholder="[email protected]"
    />
    <CoinmeTrackedTextInput
      riskIdentifier="password"
      secureTextEntry
      placeholder="password"
    />
  </>
);

6. Required values

ValueDescription
CLOUDSMITH_TEAM_TOKENPulls native CoinmeRiskSDK binaries during pod install and Gradle sync
CLIENT_IDIdentifies your app to the Risk Engine
CAAS_PARTNER_IDPartner identifier — distinct from CLIENT_ID

Troubleshooting

Native module CoinmeRiskEngine not linked

The JS layer couldn't find NativeModules.CoinmeRiskEngine at runtime.

Likely causes:

  • You added the package but didn't rebuild the native app.

    cd ios && pod install
    cd .. && yarn ios
    # and
    yarn android
  • Cloudsmith auth failing. Check the pod install / Gradle sync logs. If CocoaPods or Gradle can't reach Coinme's Cloudsmith, the native module won't link. Verify CLOUDSMITH_TEAM_TOKEN is exported in the shell you're building from, or set in ~/.gradle/gradle.properties for Gradle persistence.

  • New Architecture enabled. The module is registered on the legacy bridge only. If Fabric / TurboModules is on, the runtime won't see it. Confirm new_arch_enabled: false in Podfile and newArchEnabled=false in android/gradle.properties.

  • Metro cache. After a successful rebuild, clear Metro with yarn start --reset-cache.

Module was compiled with an incompatible version of Kotlin (Android)

The native risk-sdk-android artifact is compiled with Kotlin 2.3.0, but React Native 0.81's Gradle plugin pins an older Kotlin. Force Kotlin 2.3.0 across all subprojects via resolutionStrategy — see section 6.3, Step 2 for the full copy-pasteable block.

Expo: If the error persists after applying the resolution strategy, re-run prebuild from scratch and verify the plugin ran:

expo prebuild --clean
npx expo run:android

Check that android/build.gradle contains classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.3.0") and the // @coinme-risk-sdk:kotlin-version marker. If those are missing, the plugin didn't run — verify @coinme-security/risk-sdk-react-native is listed under plugins in your Expo config.

Could not find com.coinme:risk-sdk-android

CLOUDSMITH_TEAM_TOKEN is missing or wrong. Export it before building:

export CLOUDSMITH_TEAM_TOKEN=<your-token>

Also verify the Maven block in android/settings.gradle reads from the same env var.

Expo: Re-export and retry with:

export CLOUDSMITH_TEAM_TOKEN=<your-token>
npx expo run:android

pod install fails with 401 Authentication required

Token is wrong or expired. Re-export CLOUDSMITH_TEAM_TOKEN and run pod install again. If you have a stale cache from a previous failed attempt:

rm -rf ~/Library/Caches/CocoaPods ~/.cocoapods/repos

Expo: Re-export and retry with:

export CLOUDSMITH_TEAM_TOKEN=<your-token>
npx expo run:ios

KeyError: key not found: CLOUDSMITH_TEAM_TOKEN (Expo)

CLOUDSMITH_TEAM_TOKEN was not set when expo prebuild triggered pod install. Export it and re-run:

export CLOUDSMITH_TEAM_TOKEN=<your-token>
npx expo run:ios

NativeModules.CoinmeRiskEngine is undefined (Expo)

The native module didn't link during prebuild. Clean and rebuild:

# iOS
expo prebuild --clean && npx expo run:ios

# Android
expo prebuild --clean && npx expo run:android

RiskEngine is not initialized. Call setupCoinmeRiskEngine() first.

getSessionTag / getPartnerSessionTag / updateCoinmeRiskEngine was called before setupCoinmeRiskEngine completed. Always await setupCoinmeRiskEngine(...) first.

My integration looks fine but transactions are being rejected

Most common cause: you're not calling submitCoinmeRiskData before your server requests. This produces no error at the SDK layer — the SDK collects signals and never flushes them — but without correlated signals, Coinme's transaction services will reject the downstream request.

Checklist:

  1. await submitCoinmeRiskData() immediately before any transaction-initiating server call (checkout, payment-method link, onboarding submit, etc.)
  2. Confirm the fingerprint passed to getSessionTag / getPartnerSessionTag matches the x-device-fingerprint header on the same request — see section 9
  3. Confirm mode is 'prod' in production builds. A mismatched mode / clientId routes data to the wrong environment
  4. Confirm setupCoinmeRiskEngine has completed (await it) before any updateCoinmeRiskEngine call. Parallel setup/update races can lead to overridden config

updateCoinmeRiskEngine throws before I've called setup

By design. If no sessionKey has ever been set (via setupCoinmeRiskEngine, getSessionTag, or getPartnerSessionTag), updateCoinmeRiskEngine will throw. Call setup first.

Support

For issues, questions, or feature requests, contact your Coinme integration representative.