How To: Implement Chargeback Protection

Developers can use this documentation to implement chargeback protection for all card checkouts.

Overview

Coinflow provides multiple ways to protect merchant accounts against fraud. Learn more about fraud prevention methods before you start implementing chargeback protection.

How to Add Chargeback Protection

When merchants opt in for chargeback protection, they need to install Coinflow Purchase Protection across every page of their site. This integration gathers device and session signals during the customer’s shopping experience so Coinflow can score the purchase for fraud and chargeback risk. Depending on your implementation method, select from the options below for setup instructions:

  1. Add the <CoinflowPurchaseProtection> component to every page on your site.
  2. Add your merchantId as a property to the <CoinflowPurchaseProtection> component.
  3. In every <CoinflowPurchase> component, add the chargebackProtectionData property and input information about each purchase made via Coinflow. See Getting the Device ID if you call Coinflow APIs directly.

Coinflow provides a mobile module for both iOS and Android that exposes a getDeviceId method. Coinflow’s integrations team will share the module, sample diff files, and the application credentials needed to initialize it on sandbox and production.

Implementation

  1. Install the mobile module Coinflow provides and follow the README to wire it into your iOS and Android builds.
  2. Initialize the module on app launch using the application credentials supplied by Coinflow. The same credentials are used in sandbox and production.
  3. Call the module’s getDeviceId method to retrieve the device ID.
  4. In {"<CoinflowPurchase>"} add the chargebackProtectionData property and add information about every purchase that is being made via Coinflow, and pass the deviceId through to the component. See Getting the Device ID below.

For native iOS apps that call Coinflow’s APIs directly (not through React Native), Coinflow uses nSure’s iOS SDK to gather device and session signals.

Implementation

  1. Install the SDK using Swift Package Manager or CocoaPods:

    Swift Package Manager — in Xcode, select File > Add Package Dependencies, enter https://github.com/nsure-ai/ios-sdk, and choose version 1.3.17 or later.

    CocoaPods

    1pod 'nSure'
  2. Initialize the SDK on app launch using the appId and partnerId Coinflow’s integrations team provides. The same credentials are used on sandbox and production.

    1import nSure
    2
    3NSure.sharedInstance(withAppID: "your-app-id", partherID: "your-partner-id")
    1#import <nSure/nSure.h>
    2
    3[NSure sharedInstanceWithAppID:@"your-app-id" partherID:@"your-partner-id"];
  3. In {"<CoinflowPurchase>"} (or your direct API calls), add the chargebackProtectionData property and pass the retrieved deviceId through. See Getting the Device ID below.

  1. Add the following code to the <head> of every page on your site — not just the checkout page. After completing the chargeback protection questionnaire, Coinflow will provide your production partnerId. Use COINFTEST on sandbox.
1{"<script src=\"https://sdk.nsureapi.com/sdk.js\"> </script>"}
2<script>
3 window.nSureAsyncInit = function(deviceId) {
4 window.nSureSDK.init({
5 appId: '9JBW2RHC7JNJN8ZQ', // Remains the same on sandbox and prod
6 partnerId: 'COINFTEST' // Use COINFTEST on sandbox. Coinflow assigns your prod partnerId
7 });
8 };
9</script>

📘 This script gathers information about the user’s device, how they interact with your website, and other signals that let Coinflow’s models predict the risk of fraud or chargeback for this particular user.

  1. To each of your API calls, add the x-device-id header. Pass the value returned by calling window?.nSureSDK?.getDeviceId() on your website. See Getting the Device ID below.

This deviceId is how Coinflow ties an individual request back to the device and session signals collected by the script you installed above.

  1. In the Checkout Endpoint and the Redeem Transaction Endpoint, pass the chargebackProtectionData.

📘 This information describes what is being purchased, which lets Coinflow’s models determine the risk of chargeback for this particular purchase.

Getting the Device ID

When chargeback protection is enabled, pass the device ID in the x-device-id header on checkout, redeem, and subscription API calls. How you retrieve it depends on your integration:

After the protection script has loaded and initialized on the page, read the device ID before making your Coinflow API request:

1const deviceId = window?.nSureSDK?.getDeviceId();
2
3// Include on Coinflow API requests
4headers: {
5 'x-device-id': deviceId,
6}

Mount <CoinflowPurchaseProtection> on every page, then use the useCoinflowProtectionHeaders hook from @coinflowlabs/react:

1import {
2 CoinflowPurchaseProtection,
3 useCoinflowProtectionHeaders,
4} from '@coinflowlabs/react';
5
6function App() {
7 return (
8 <>
9 <CoinflowPurchaseProtection
10 coinflowEnv="sandbox"
11 merchantId="your-merchant-id"
12 />
13 {/* Your app content */}
14 </>
15 );
16}
17
18function Checkout() {
19 const getProtectionHeaders = useCoinflowProtectionHeaders();
20 const deviceId = getProtectionHeaders()['x-device-id'];
21
22 // Include on Coinflow API requests
23 headers: {
24 ...getProtectionHeaders(),
25 }
26}

If you use <CoinflowPurchase>, the device ID is sent automatically — you only need to retrieve it manually when calling Coinflow APIs directly from your backend-for-frontend or custom checkout flow.

Coinflow provides a native module for iOS and Android. Initialize it on app launch using the credentials Coinflow supplies, then call getDeviceId and pass the result to <CoinflowPurchase> or your API layer:

1import { CoinflowPurchase } from '@coinflowlabs/react-native';
2
3// After initializing the native module at app launch:
4const deviceId = await nativeModule.getDeviceId();
5
6<CoinflowPurchase
7 deviceId={deviceId}
8 chargebackProtectionData={[/* ... */]}
9 {/* other props */}
10/>

Contact Coinflow’s integrations team for the module, sample diff files, and initialization credentials.

After initializing the SDK, read the device ID:

1let deviceId = NSure.sharedInstance.deviceId

Pass this value to your merchant server so it can be included as the x-device-id header on Coinflow API requests.

What to pass into chargebackProtectionData

Merchants that opt-in for chargeback protection are required to pass chargebackProtectionData as a prop to the <CoinflowPurchase> component or to our card checkout, saved card checkout, ach checkout , and redeem transaction endpoints.

🚧 The more information that you pass here the better the authorization rates will be, so it is in your best interest to supply as much information as possible

Example of chargebackProtectionData data structure

1{
2 /**
3 * The name of the product
4 */
5 productName: string;
6 /**
7 * The product type. Possible values include: inGameProduct, gameOfSkill, dataStorage, computingResources, sportsTicket, eSportsTicket, musicTicket, conferenceTicket, virtualSportsTicket, virtualESportsTicket, virtualMusicTicket, virtualConferenceTicket, alcohol, DLC, subscription, fundACause, realEstate, computingContract, digitalArt, topUp
8 * Contact Coinflow for the productType value.
9 */
10 productType: 'inGameProduct' |
11 'gameOfSkill' |
12 'dataStorage' |
13 'computingResources' |
14 'sportsTicket' |
15 'eSportsTicket' |
16 'musicTicket' |
17 'conferenceTicket' |
18 'virtualSportsTicket' |
19 'virtualESportsTicket' |
20 'virtualMusicTicket' |
21 'virtualConferenceTicket' |
22 'alcohol' |
23 'DLC' |
24 'subscription' |
25 'fundACause' |
26 'realEstate' |
27 'computingContract' |
28 'digitalArt' |
29 'topUp';
30 /**
31 * The number of units sold
32 */
33 quantity: number;
34 /**
35 * Any additional data that the store can provide on the product, e.g. description, link to image, etc.
36 */
37 rawProductData?: { [key: string]: any };
38}

Travel bookings

Merchants selling flights and hotel reservations should declare each booking with a travel-specific itemClass so the purchase is evaluated with travel-specific fraud signals — itinerary, lodging, PNR, trip, and flight details — instead of being treated as a generic product. Choose the item class that matches what is being sold:

itemClassUse for
travelA combined flight + hotel package
flightTicketA flight ticket on its own
flightUpgradeAn upgrade to an existing flight booking (seats, baggage, fare changes, insurance)
lodgingA hotel reservation on its own
If itemClass is omitted, the cart item is treated as a generic product and travel-specific fraud signals are not evaluated, which can reduce approval rates for travel transactions.

Travel package data structure (itemClass: 'travel')

1{
2 /**
3 * Identifies this cart item as a travel booking
4 */
5 itemClass: 'travel';
6 /**
7 * The cart item's unique ID
8 */
9 id?: string;
10 /**
11 * The item's selling price
12 */
13 sellingPrice: {
14 valueInCurrency: number; // e.g. 1250.00
15 currency: string; // ISO 4217, e.g. 'USD'
16 };
17 /**
18 * The item's list price
19 */
20 listPrice?: {
21 valueInCurrency: number;
22 currency: string;
23 };
24 /**
25 * The lodging details of the booking
26 */
27 lodging: {
28 reservationId: string; // Hotel reservation ID, e.g. 'X36Q9C'
29 nights: number; // Number of nights booked (min 1)
30 checkInDate: string; // Check-in date, e.g. '2026-09-02'
31 checkInHour: string; // Hour of day of check-in, '00'-'23'
32 hotel: {
33 name: string; // e.g. 'The Plaza Hotel'
34 country: string; // Two-letter ISO 3166 code, e.g. 'US'
35 city: string; // e.g. 'New York'
36 };
37 wereExtrasPurchased: boolean; // Whether extras were purchased
38 isRefundable: boolean; // Whether the reservation is refundable
39 highestRoomType: string; // Most luxurious room booked, e.g. 'Presidential Suite'
40 numberOfRooms: number; // Rooms booked (min 1)
41 numberOfGuests: number; // Guests in the reservation (min 1)
42 numberOfKids: number; // Kids out of the number of guests (min 0)
43 };
44 /**
45 * The flight ticket's PNR code
46 */
47 pnr: string; // e.g. 'X36Q9C'
48 /**
49 * The trip details
50 */
51 trip: {
52 numberOfFlights: number; // Flights in the trip (min 1)
53 numberOfCheckedBags: number; // Checked bags (min 0)
54 firstWayDestination: string; // Three-letter IATA airport code, e.g. 'JFK'
55 isRoundTrip: boolean; // true if the trip is a round-trip
56 highestClass: 'economy' | 'luxurious'; // 'luxurious' if the highest travel class is business/first
57 isFlexibleTicket: boolean; // true if the ticket is flexible
58 wereExtrasPurchased: boolean; // true if extras were included
59 };
60 /**
61 * The first flight of the trip
62 */
63 firstFlight: {
64 numberOfTravelers: number; // Travelers on the first flight (min 1)
65 departureTime: number; // Departure timestamp in *milliseconds* since the unix epoch
66 departureAirport: string; // Three-letter IATA code, e.g. 'JFK'
67 duration: number; // Flight duration in hours, e.g. 3.5
68 flightNumber: string; // Alphanumeric flight code, e.g. 'BA2491A'
69 };
70 /**
71 * Any additional data that the store can provide on the booking
72 */
73 rawProductData?: { [key: string]: any };
74}

Flight-only bookings (itemClass: 'flightTicket')

For a flight ticket sold without lodging, use itemClass: 'flightTicket'. It has the same required pnr, trip, and firstFlight fields as the travel package above, but no lodging:

1{
2 itemClass: 'flightTicket';
3 id?: string;
4 sellingPrice: {valueInCurrency: number, currency: string};
5 listPrice?: {valueInCurrency: number, currency: string};
6 pnr: string; // The flight ticket's PNR code
7 trip: {/* same shape as travel */};
8 firstFlight: {/* same shape as travel */};
9 rawProductData?: { [key: string]: any };
10}

Flight upgrades (itemClass: 'flightUpgrade')

For add-ons to an existing flight booking — seat selection, extra baggage, fare changes, or travel insurance — use itemClass: 'flightUpgrade':

1{
2 itemClass: 'flightUpgrade';
3 id?: string;
4 sellingPrice: {valueInCurrency: number, currency: string};
5 listPrice?: {valueInCurrency: number, currency: string};
6 /**
7 * The PNR code of the booking being upgraded
8 */
9 pnr?: string;
10 /**
11 * The type of upgrade
12 */
13 upgradeType: 'changes' | 'fareIncrease' | 'baggage' | 'seats' | 'insurance' | 'other';
14 /**
15 * The upgrade description — required when upgradeType is 'other'
16 */
17 description?: string;
18 rawProductData?: { [key: string]: any };
19}

Hotel-only bookings (itemClass: 'lodging')

For a hotel reservation sold without flights, use itemClass: 'lodging'. The reservation fields sit at the top level of the item (the same fields as the lodging object of the travel package):

1{
2 itemClass: 'lodging';
3 id?: string;
4 sellingPrice: {valueInCurrency: number, currency: string};
5 listPrice?: {valueInCurrency: number, currency: string};
6 reservationId: string; // e.g. 'X36Q9C'
7 nights: number; // min 1
8 checkInDate: string; // e.g. '2026-09-02'
9 checkInHour: string; // '00'-'23'
10 hotel: {
11 name: string;
12 country: string; // Two-letter ISO 3166 code
13 city: string;
14 };
15 wereExtrasPurchased: boolean;
16 isRefundable: boolean;
17 highestRoomType: string;
18 numberOfRooms: number; // min 1
19 numberOfGuests: number; // min 1
20 numberOfKids: number; // min 0
21 rawProductData?: { [key: string]: any };
22}

Example travel booking

The values below are examples only — replace them with the real details of each booking.
1{
2 "itemClass": "travel",
3 "id": "5de33332-546a-4171-8988-2a43d2bfe9c6",
4 "sellingPrice": {
5 "valueInCurrency": 1250.00,
6 "currency": "USD"
7 },
8 "lodging": {
9 "reservationId": "X36Q9C",
10 "nights": 3,
11 "checkInDate": "2026-09-02",
12 "checkInHour": "15",
13 "hotel": {
14 "name": "The Plaza Hotel",
15 "country": "US",
16 "city": "New York"
17 },
18 "wereExtrasPurchased": false,
19 "isRefundable": true,
20 "highestRoomType": "Deluxe King",
21 "numberOfRooms": 1,
22 "numberOfGuests": 2,
23 "numberOfKids": 0
24 },
25 "pnr": "X36Q9C",
26 "trip": {
27 "numberOfFlights": 2,
28 "numberOfCheckedBags": 1,
29 "firstWayDestination": "JFK",
30 "isRoundTrip": true,
31 "highestClass": "economy",
32 "isFlexibleTicket": false,
33 "wereExtrasPurchased": false
34 },
35 "firstFlight": {
36 "numberOfTravelers": 2,
37 "departureTime": 1788373800000,
38 "departureAirport": "SFO",
39 "duration": 5.5,
40 "flightNumber": "AA1234"
41 },
42 "rawProductData": {
43 "packageName": "NYC Getaway — Flight + Hotel",
44 "bookingChannel": "web"
45 }
46}

Example Implementation on Coinflow’s Prebuilt UI

1<CoinflowPurchase
2 wallet={wallet}
3 merchantId={process.env.REACT_APP_MERCHANT_ID as string}
4 transaction={transaction}
5 amount={amount}
6 chargebackProtectionData={[{
7 "productName": "Sword", // Name of Product
8 "productType": "inGameProduct", // Get the value from Coinflow
9 "quantity": 1,
10 "rawProductData": { // Adjust based on the available product data
11 "productID": "sword12345",
12 "productDescription": "A legendary sword with magical powers.",
13 "productCategory": "Weapon",
14 "weight": "15 lbs",
15 "dimensions": "40 in x 5 in",
16 "origin": "Ancient Kingdom",
17 "craftedBy": "Master Blacksmith",
18 "craftingDate": "2024-06-19"
19 }
20 },]}
21/>

Example Implementation on Coinflow’s APIs

$curl --request POST \
> --url https://api-sandbox.coinflow.cash/api/checkout/ach/merchantId \
> --header 'accept: application/json' \
> --header 'content-type: application/json' \
> --data '
>{
> "subtotal": {
> "cents": 100
> },
> "token": "5a000000-0000-0000-0000-000000000000",
> "chargebackProtectionData": [
> {
> "productType": "inGameProduct",
> "productName": "Sword",
> "quantity": 1,
> "rawProductData": {
> "productID": "sword12345",
> "productDescription": "A legendary sword with magical powers.",
> "productCategory": "Weapon",
> "weight": "15 lbs",
> "dimensions": "40 in x 5 in",
> "origin": "Ancient Kingdom",
> "craftedBy": "Master Blacksmith",
> "craftingDate": "2024-06-19"
> }
> }
> ]
>}
>'