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
| Requirement | Supported |
|---|---|
| Node | 20.9+ |
| React | 18+ |
| React Native | 0.72 – 0.81 (see note below) |
| iOS | 16.0+ (required by the underlying CoinmeRiskSDK xcframework) |
| Android | minSdkVersion 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:
- The private npm registry at
npm.coinme.com(for the JS package) - The CocoaPods repository (for the iOS native artifact)
- 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=trueFor 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-nativeAutolinking 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:
- 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/"- Set the iOS deployment target.
CoinmeRiskSDKrequires iOS 16+ and RN's default is lower:
platform :ios, '16.0'- Disable the New Architecture by passing
new_arch_enabled: falsetouse_react_native!(or setRCT_NEW_ARCH_ENABLED=0at 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 installAutolinking 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 rootbuild.gradleinstead ofdependencyResolutionManagement(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:
allprojectsmust be at the root level ofbuild.gradle, not nested insidebuildscript.
Step 3 — Disable the New Architecture.
In android/gradle.properties:
newArchEnabled=falseStep 4 — Build.
export CLOUDSMITH_TEAM_TOKEN=<your-token>
cd android && ./gradlew cleanAutolinking 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:
| Permission | Usage |
|---|---|
INTERNET | Network communication for fraud detection |
ACCESS_COARSE_LOCATION / ACCESS_FINE_LOCATION | Location signals for fraud detection |
ACCESS_WIFI_STATE | WiFi name and status |
ACCESS_NETWORK_STATE | Network information and state |
READ_PHONE_STATE | Phone/network info (MNC, MCC, IMEI, phone number, phone type, SIM number) |
USE_BIOMETRIC / USE_FINGERPRINT | Biometric authentication settings (Face / Fingerprint) |
WRITE_EXTERNAL_STORAGE | Write data to check re-installation behavior |
READ_EXTERNAL_STORAGE | External storage status, total size, free size |
READ_GSERVICES | Device 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: truein the setup options. Do not enablesetCloudEntitlementswithout adding the CloudKit capability — the underlying native SDK will crash.
5. App Store Connect requirements
Before releasing, update App Store Connect → App Privacy:
- Data Collection — select "Yes, we collect data from this app"
- Location — select Coarse and/or Precise Location. Purpose: App Functionality. Reason: "Required for fraud detection and prevention."
- 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(orgetPartnerSessionTag) →updateCoinmeRiskEnginewith the returnedwebSessionID→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-info—getUniqueId()is stable per installexpo-application—getAndroidId()/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
- Stable across app sessions for the same install
- Identical on every surface that touches risk — the
fingerprintoption passed togetSessionTag/getPartnerSessionTag, and thex-device-fingerprintheader on every subsequent BE call in the same session - 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)
- Setup once per app session.
sessionKeyis optional at setup — the native SDK auto-generates one if omitted. After callinggetSessionTag/getPartnerSessionTag, pass the returnedwebSessionIDtoupdateCoinmeRiskEngine. - 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.
- 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
<CoinmeTrackedTextInput> — the recommended pathA 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
riskIdentifierfor both the text and focus calls on a given field - Use descriptive, stable identifiers:
"email","password","cardNumber"— not"input1" - Prefer
CoinmeTrackedTextInputwhen 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
trackRiskPageoruseTrackCoinmeRiskPageexport.
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
| Export | Description |
|---|---|
setupCoinmeRiskEngine | Initialize the native Risk Engine. sessionKey is optional — omit at setup, then pass the webSessionID from the session-tag call via updateCoinmeRiskEngine. |
updateCoinmeRiskEngine | Merge config updates (customerId, flow, sessionKey, …) into the cached session. Throws if called before setup. |
getCoinmeRiskEngineConfig | Returns the locally cached config. Never round-trips to native. |
clearSessionKey | Clears the in-memory session key. Use on logout / account switch. |
submitCoinmeRiskData | Flushes collected signals to the Risk Engine. Call before every transaction-initiating server request. |
getSessionTag | Calls Coinme's authenticated /get-session-tag endpoint. Caches the returned webSessionID as the active session key. |
getPartnerSessionTag | Calls Coinme's public partner session-tag endpoint. URL is auto-resolved from mode. |
trackRiskTextChange | Imperative text-change tracker. |
trackRiskFocusChange | Imperative focus / blur tracker. |
Components
| Export | Description |
|---|---|
CoinmeTrackedTextInput | TextInput wrapper that tracks change / focus / blur by riskIdentifier. Forwards all other props unchanged. |
Enums and types
| Type | Description | ||||
|---|---|---|---|---|---|
FlowType | Enum — Onboarding, CardTransaction, CardLinking. | ||||
Mode | `'test' | 'prod'`. | |||
AppRegion | `'default' | 'eu' | 'ca' | 'in' | 'au'`. Mirrors the native SDKs. |
RiskEngineSetupOptions | Options for setupCoinmeRiskEngine: mode, clientId, sessionKey?, flow?, customerId?, partnerId?, region?, enableBehaviorBiometrics?, enableClipboardTracking?, enableFieldTracking?. | ||||
RiskEngineUpdateOptions | Partial update shape: sessionKey?, flow?, customerId?, … | ||||
RiskEngineConfig | Shape of the cached config returned from getCoinmeRiskEngineConfig. | ||||
GetSessionTagOptions | Options for getSessionTag: apiBaseUrl, authToken, fingerprint, timeout?, additionalHeaders?. | ||||
GetPartnerSessionTagOptions | Options for getPartnerSessionTag: partnerId, customerId, fingerprint, rampId?, timeout?, additionalHeaders?. | ||||
SessionTagData | { webSessionID: string; orgID?: string }. | ||||
CoinmeTrackedTextInputProps | Extends React Native's TextInputProps with a required riskIdentifier: string. |
Setup options in detail
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
mode | Mode | Yes | — | 'test' or 'prod' |
clientId | string | Yes | — | Your Coinme-issued client ID |
partnerId | string | No | — | Partner identifier (distinct from clientId) |
customerId | string | No | — | Customer / user identifier |
sessionKey | string | No | auto | Session key — auto-generated if omitted |
flow | FlowType | No | — | Onboarding, CardTransaction, or CardLinking |
region | AppRegion | No | default | default (US), eu, ca, au, in |
enableBehaviorBiometrics | boolean | No | true | Enable behavioral biometrics collection |
enableClipboardTracking | boolean | No | false | Enable clipboard activity tracking |
enableFieldTracking | boolean | No | true | Enable automatic field tracking |
clientIdandpartnerIdare 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
minSdkVersion24+ 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-native2. 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.
| Platform | What the plugin configures |
|---|---|
iOS Podfile | Adds Cloudsmith CocoaPods source (token read at pod install time via ENV.fetch) |
iOS Podfile | Sets deployment target to 16.0 |
Android build.gradle | Adds Cloudsmith Maven repository (token read at build time via System.getenv) |
Android build.gradle | Forces Kotlin 2.3.0 and adds resolution strategies |
iOS:
npx expo run:iosAndroid:
npx expo run:androidThese 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 --cleanbeforenpx 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_IDandCAAS_PARTNER_IDare 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
| Value | Description |
|---|---|
CLOUDSMITH_TEAM_TOKEN | Pulls native CoinmeRiskSDK binaries during pod install and Gradle sync |
CLIENT_ID | Identifies your app to the Risk Engine |
CAAS_PARTNER_ID | Partner identifier — distinct from CLIENT_ID |
Troubleshooting
Native module CoinmeRiskEngine not linked
Native module CoinmeRiskEngine not linkedThe 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. VerifyCLOUDSMITH_TEAM_TOKENis exported in the shell you're building from, or set in~/.gradle/gradle.propertiesfor 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: falseinPodfileandnewArchEnabled=falseinandroid/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)
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:androidCheck 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
Could not find com.coinme:risk-sdk-androidCLOUDSMITH_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:androidpod install fails with 401 Authentication required
pod install fails with 401 Authentication requiredToken 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/reposExpo: Re-export and retry with:
export CLOUDSMITH_TEAM_TOKEN=<your-token>
npx expo run:iosKeyError: key not found: CLOUDSMITH_TEAM_TOKEN (Expo)
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:iosNativeModules.CoinmeRiskEngine is undefined (Expo)
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:androidRiskEngine is not initialized. Call setupCoinmeRiskEngine() first.
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:
await submitCoinmeRiskData()immediately before any transaction-initiating server call (checkout, payment-method link, onboarding submit, etc.)- Confirm the
fingerprintpassed togetSessionTag/getPartnerSessionTagmatches thex-device-fingerprintheader on the same request — see section 9 - Confirm
modeis'prod'in production builds. A mismatchedmode/clientIdroutes data to the wrong environment - Confirm
setupCoinmeRiskEnginehas completed (await it) before anyupdateCoinmeRiskEnginecall. Parallel setup/update races can lead to overridden config
updateCoinmeRiskEngine throws before I've called setup
updateCoinmeRiskEngine throws before I've called setupBy 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.
Updated 2 days ago