Carbon

Documentation

Welcome to Carbon's docs!

Get Started    

Credit/Debit Purchases API

const axios = require("axios");
const ROOT = process.env.NODE_ENV === "PRODUCTION" ? "https://api.carbon.money" : "https://sandbox.carbon.money";

Support Spec

Location (Blocked Countries) Support

The location (blocked countries) support section is here. If you are integrating the API, make sure to pay attention to implementing necessary geoblocking on your end. If you are integrating the widget, geoblocking is applied automatically.

Cryptocurrency Support

The cryptocurrency support section is here. This section includes the full names of the supported cryptocurrencies and the set of symbols you can pass in under the 'cryptocurrencySymbol' request parameter in the /v1/card/charge3D endpoint. More on that below.

Fiat Currency Support

The fiat currency support section is here.

FX currency conversion means that we charge a user's credit/debit card in their local currency but the charge settles in the base currency. This is all handled behind the scenes. Specific FX rates are determined by the user's issuing bank at the time of the charge. We take on all FX fees.

Regarding terminology, we will generally refer to the currency we charge the user's credit/debit card in as the 'charge currency'. We refer to the currency that the charge ultimately settles in our merchant bank account as the 'base currency' or 'settlement currency'. You can specify the base currency for settlement in the endpoints below say for receiving your superuser charges (if applicable) or the fiat amount of the purchase minus our fees if you are allocating us a loan or otherwise providing liquidity.

For more on settlement, go here.

Subunits

Some fiat currencies do not have subunits and some do such as two-place decimal subunits ($1.99 for example for USD). For consistency and simplicity we express all fiat amounts in our fiat > crypto API without any decimal points and so the decimal place is moved two places to the right adding 0's as necessary. So $102.89 will be expressed as $10289 and 5.00 EUR will be expressed as 500 EUR.

Transaction Limits

The transaction limits section is here

Fees

The fees section is here

1. API Request for current fiat & crypto exchange rates (GET)

Cached Data

Rates are cached every 5 minutes for crypto & 30 minutes for fiat. We return back "rateSignature" in the headers of the response, which corresponds to a unique hash. If the hash is the same as the one before, you know the data has not changed. If it changes, then you should update.

Payment Gateway Considerations

If you are integrating with us as a payment gateway and providing quotes as a market maker/liquidity provider, please by and large ignore this section. You will instead be passing in your fiat > crypto rates later when your user is about to charge their credit/debit card for a crypto purchase. However, you can use the rates endpoint and just pass in the 'fiatBaseCurrency' parameter to obtain our FX rates with USD as the exchange currency against our settlement currencies.

No Super JWT Required for Rates

Super JWT, Contact ID, and Surcharge Application

If you pass in your super JWT and you have surcharges applied for your superuser, then rates will be adjusted to reflect your surcharges in both sandbox and production. For more on surcharges, check out this. If you pass in a contactID as well, then limit checking will apply.

Fiat Currency (FX) and Crypto Exchange Rates Conventions

We return a rates object for each of the cryptocurrencies specified with fiat currency and crypto exchange rates. However, our convention is that the base currency is in the denominator and the quote currency is in the numerator. For example 'eur/usd' fx will return the value of 1 USD in Euros and 'btc/usd' exchange rates will return the value of 1 USD in BTC.
Note that the returned 'txFee' fee is in USD value.
If you just pass in 'fiatBaseCurrency' into the /v1/rates endpoint, then just FX rates from USD to the specified 'fiatBaseCurrency' will be returned.

Note that FX is from USD to 'fiatBaseCurrency'. FX from the user's local currency of their credit/debit card to the settlement currency is automatic and specific rates are determined by the user's issuing bank.

Example request/response

// please pass in the superuser jwt if you want to authenticate your superuser
// please pass in the contactId as a url query parameter if you want to authenticate the contact

let url = `${ROOT}/v1/rates?cryptocurrencyArray=btc,eth,eos,trx&fiatBaseCurrency=eur&fiatChargeAmount=10000;

axios.get(url).then(result => console.log).catch(err => console.log);
{ btc: 
   { available: true,
     max: '2500.00',
     name: 'Bitcoin',
     'eur/usd': '0.9155',
     'btc/usd': '0.000121',
     'usd/btc': '8232.296562',
     'btc/eur': '0.000132679566',
     'eur/btc': '7536.955633',
     txFee: '3.00',
     estimatedCryptoPurchase: '0.01326796' },
  eth: 
   { available: true,
     max: '2012.38',
     name: 'Ethereum',
     'eur/usd': '0.9155',
     'eth/usd': '0.005908',
     'usd/eth': '169.255683',
     'eth/eur': '0.006453298997',
     'eur/eth': '154.959502',
     txFee: '3.00',
     estimatedCryptoPurchase: '0.64532990' },
  eos: 
   { available: true,
     max: '1841.94',
     name: 'EOS',
     'eur/usd': '0.9155',
     'eos/usd': '0.352669',
     'usd/eos': '2.835523',
     'eos/eur': '0.385204979414',
     'eur/eos': '2.596020',
     txFee: '3.00',
     estimatedCryptoPurchase: '38.52049794' },
  trx: 
   { available: true,
     max: '2500.00',
     name: 'Tron',
     'eur/usd': '0.9155',
     'trx/usd': '76.595127',
     'usd/trx': '0.013056',
     'trx/eur': '83.661604323153',
     'eur/trx': '0.011953',
     txFee: '3.00',
     estimatedCryptoPurchase: '8366.16043232' },
  message: 'Successfully pulled exchanges rates'
}
// 400, invalid fiatBaseCurrency
{
  "message": "fiatBaseCurrency query parameter is invalid.  Refer to https://docs.carbon.money/docs/fiat-currencies for valid symbol names.",
  "code": 400
}

// 400, invalid cryptocurrencyArray
{
  "message": "cryptocurrencyArray query parameter is invalid. Refer to https://docs.carbon.money/docs/cryptocurrencies for valid symbol names.",
  "code": 400
}
let url = `${ROOT}/v1/rates?fiatBaseCurrency=eur';

axios.get(url).then(result => console.log).catch(err => console.log);
{ 
  'eur/usd': '0.92', 
  'usd/eur': '1.09' 
}

How do I check if rates have changed?

We pass back rateSignature under the response headers, which will be a unique hash signature. If the rates have changed, the rateSignature will change.

example response rate signature: 150519c2d24b7717001b202ab25582ac83ce07d15855509781e7db46d6efb7c5

You also have the option of passing in the rateSignature to the purchase crypto (see below) routes and if the rates are expired, the call will fail. In particular, we will return a 412 status code.

Amount Number Format

All fiat amounts in the request will be read without decimals. So $102.89 will be expressed as 10289.

Request parameters

Parameters
Access
Description

cryptocurrencyArray

required (unless just 'fiatBaseCurrency' specified)

A comma-delimited array of symbol(s) of the cryptocurrency you are interested in procuring the fiat > crypto rates for.

Check out this for the list of tokens we currently support and their symbols.

If you just want to pull our current FX rates, 'fiatBaseCurrency' and not 'cryptocurrencyArray' is required.

fiatBaseCurrency

required

The settlement currency for the crypto purchase. Case-insensitive. Must be a valid three-character ISO 4217 currency code. For a list of ISO 4217 codes go to this link: https://www.iban.com/currency-codes and check out our list of supported fiat base currencies here. Note that as mentioned above FX from the user's local currency of their credit/debit card to the settlement currency is automatic and specific rates are determined by the user's issuing bank. Note that if you just want to pull our current FX rates, 'fiatBaseCurrency' and not 'cryptocurrencyArray' is required

fiatChargeAmount

optional

How much you'd like pay in your base currency. Adding this parameter will give you an additional field in the response that gives you an estimate of how much cryptocurrency ('estimatedCryptoPurchase' ) under the rates object for each token in the specified 'cryptocurrencyArray' in addition to the transaction fee in the 'txFee' field.

"1089" will be calculated as "10.89" in your base currency for example.

"max" purchase values

In the response, you will see the exchange rates for different fiat currencies under each asset object. You also will notice the "max" field. This refers to the max (USD) fiat purchase allowed for that asset for KYC'd users. In general, this will be 2500.00 USD. However, in the event that our treasury is running low, this value could be lower.

2. Adding a credit/debit card (POST)

Super JWT and Contact ID Required

Sandbox Testing

For card details to test in the sandbox environment, refer back to Sandbox Testing Information

Tokenized Credit/Debit Card Fields

To tokenize credit/debit card data and to add a payment card with these tokenized fields, please check out this.

Card Protection Checks and Anti-Fraud

In production, note that if a contacts enters in credit/debit information incorrectly too many times in a row, they will be blocked. Moreover, a contact in production cannot add a credit/debit card that is already under another contact. These checks are in place to prevent spamming and fraudulent behavior. All contacts must email [email protected] to request clearance of their account. We also implement some more conservative transaction limits and blocking dependent upon the issuing country of the card among other factors.

We will try as hard as we can to be clear and transparent with our anti-fraud policies but by our judgement we can and will adapt our anti-fraud policies at any time to protect our users. Our goal is to maximize the number and size of transactions while minimizing fraud and we plan for our anti-fraud to become more fine-grained over time. If you have any questions or concerns, please contact [email protected]

Note that the below error response documentation will include messages and other logging for our anti-fraud system (WIP).

Example request/response

let jwtToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJlMzE3YjdlNy0yMzQ1LTQ0MWMtODA0Ni1kYjgxNTkyYmEyN2YiLCJzdXBlclVzZXIiOnRydWUsImNvbnRhY3QiOmZhbHNlLCJlbWFpbCI6ImRhbmllbEBjYXJib24ubW9uZXkiLCJpYXQiOjE1NTczMjc5MTR9.WZnSR5N1FebmT9nMu97PJvku49NY0jk4aKVPKm_1MlM';

let headers = {
  headers: {
    Authorization: `Bearer ${jwtToken}`
  }
};
let url = `${ROOT}/v1/card/addNew`;
let data = {  
  nameOnCard: "Satoshi Nakamoto",
  cardNumber: "5100000000000511",
  expiry: "12/2030",
  cvc: "123",
  billingPremise: "No 789",
  billingStreet: "The Street",
  billingPostal: "55555",
  contactId: "ab5bb41b-5979-4a54-b734-23eb9076188e",
  rememberMe: "true",
  fiatBaseCurrency: "USD"
}

axios.post(url, data, headers).then(result => console.log(result)).catch(err => console.log(err));
{
  "message": "Card added successfully!",
  "details": {
    "creditDebitId": "6c2091a2-1f14-478f-8e18-9f131cc7fb09",
    "contactId": "3c2091a2-1f14-478f-8e18-9f131cc7fb09"
  },
   "code": 200
}
// 400
{
 "message": 'fiatBaseCurrency (cad) is not supported. Please refer to https://docs.carbon.money/docs/fiat-currencies for a list of settlement currencies we support.',
 "code": 400
}


// code 400
// the following error responses are for account checks
// on credit/debit card data
// note that certain subsets of the messages will appear together
// as a single string with the messages delimited by semicolons 
// for example this may apply
// if both the CVC and expiration date for a credit/debit card
// are evaluated as incorrect by the issuing bank behind the card
// There may also be a 'Incorrect Field(s): ' prefix for account check messages
{
  "message": "AMEX currently not supported",
  "code": 400
}

{
  "message": "Only Visa and Mastercard accepted right now",
  "code": 400
}

{
  "message": "Incorrect CVC and/or Expiration Date",
  "code": 400
}

{
  "message": "Incorrect Postal Code",
  "code": 400
}

{
  "message": "Incorrect Billing Premise",
  "code": 400
}


// code 500 
{
  "message": "There was an error adding the credit/debit card.",
  "code": "500"
}

Request parameters

Parameters
Access
Description

nameOnCard

optional

cardNumber

required

Must be a string with no spaces or other special characters.

expiry

required

Must be a string in the format 'MM/YYYY' or 'MM/YY'

cvc

required

Must be a string. The security code for the credit/debit card. Also referenced by various other terminology including CVV, CSC, CVD, or CVN.

billingPremise

required

Must be a string. The house number or first line for the billing address.

billingStreet

optional

Must be a string.

billingPostal

required

Must be a string. The billing postal code.

contactId

required

rememberMe

required

Whether or not to return a reference to the added credit/debit card. Must be a boolean. A credit/debit id will not be returned if 'rememberMe' is false.

fiatBaseCurrency

required

The settlement currency for the crypto purchase. Case-insensitive. Must be a valid three-character ISO 4217 currency code. For a list of ISO 4217 codes go to this link: https://www.iban.com/currency-codes and check out our list of supported fiat base currencies here. Note that as mentioned above FX from the user's local currency of their credit/debit card to the settlement currency is automatic and specific rates are determined by the user's issuing bank. Note that if you just want to pull our current FX rates, 'fiatBaseCurrency' and not 'cryptocurrencyArray' is required

Account Check Aside

If you want to just run account checks without storing tokenized credit/debit cards for verification purposes, pass in the boolean true for 'accountCheck' in the request body.

3. Get credit/debit card information (GET)

Example request/response

let jwtToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJlMzE3YjdlNy0yMzQ1LTQ0MWMtODA0Ni1kYjgxNTkyYmEyN2YiLCJzdXBlclVzZXIiOnRydWUsImNvbnRhY3QiOmZhbHNlLCJlbWFpbCI6ImRhbmllbEBjYXJib24ubW9uZXkiLCJpYXQiOjE1NTczMjc5MTR9.WZnSR5N1FebmT9nMu97PJvku49NY0jk4aKVPKm_1MlM';

let headers = {
  headers: {
    Authorization: `Bearer ${jwtToken}`
  }
};

let url = `${ROOT}/v1/card?contactId=ab5bb41b-5979-4a54-b734-23eb9076188e&creditDebitId=be4c48bb-e9a5-4ec3-86e8-490b8d0fca30`;
	
axios.get(url, headers).then(result => console.log(result)).catch(err => console.log(err));
{ message: 'Successfully retrieved credit/debit card information by contactId and creditDebitId.',
  details: 
   { cardNumber: 'xxxx-xxxx-xxxx--0511',
     timeAdded: 'Tue Oct 01 2019 18:40:20 GMT+0000 (UTC)',
     fiatBaseCurrency: 'USD' 
   } 
}
let jwtToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJlMzE3YjdlNy0yMzQ1LTQ0MWMtODA0Ni1kYjgxNTkyYmEyN2YiLCJzdXBlclVzZXIiOnRydWUsImNvbnRhY3QiOmZhbHNlLCJlbWFpbCI6ImRhbmllbEBjYXJib24ubW9uZXkiLCJpYXQiOjE1NTczMjc5MTR9.WZnSR5N1FebmT9nMu97PJvku49NY0jk4aKVPKm_1MlM';

let headers = {
  headers: {
    Authorization: `Bearer ${jwtToken}`
  }
};

let url = `${ROOT}/v1/card?contactId=ab5bb41b-5979-4a54-b734-23eb9076188e`;
	
axios.get(url, headers).then(result => console.log(result)).catch(err => console.log(err));
{ message: 'Successfully retrieved credit/debit card information by contactId.',
  details: 
   [ { creditDebitId: '8e5f6e8a-3d8e-4831-a70f-99bcb074ad2b',
       timeAdded: '2019-08-20T16:05:38.000Z',
       cardNumber: 'xxxx-xxxx-xxxx--0511',
       fiatBaseCurrency: 'USD' },
     ...
   ]
}
// 404
{
  "message":"Card by contactId and creditDebitId not found.",
  "code":"404"
}

// 404
{
  "message":"Cards by contactId not found.",
  "code":"404"
}

// 500
{
  "message":"Error getting card(s) by contactId and/or creditDebitId.",
  "code":"500"
}

Request parameters

Parameters
Access
Description

contactId

required

creditDebitId

optional

Returns all of contact's cards if omitted

4. Delete credit/debit card (DELETE)

let jwtToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJlMzE3YjdlNy0yMzQ1LTQ0MWMtODA0Ni1kYjgxNTkyYmEyN2YiLCJzdXBlclVzZXIiOnRydWUsImNvbnRhY3QiOmZhbHNlLCJlbWFpbCI6ImRhbmllbEBjYXJib24ubW9uZXkiLCJpYXQiOjE1NTczMjc5MTR9.WZnSR5N1FebmT9nMu97PJvku49NY0jk4aKVPKm_1MlM';

let headers = {
  headers: {
    Authorization: `Bearer ${jwtToken}`
  }
};

let creditDebitCardId = 'exampleCreditDebitCardId';
let url = `${ROOT}/v1/card/${creditDebitCardId}`;


axios.delete(url, headers)
    .then(resp => {console.log(resp.data)})
    .catch(err => {console.log(err)})
// response with no body and status 204 returned
// 401
{
  "message": "Credit/debit card not found",
  "code": 401
}

// 500
{
  "message": "Error deleting credit/debit card",
  "code": 500
}

5. Charge credit/debit card (POST)

What is 3-D Charge?

A 3-D charge uses the 3-D Secure protocol designed to reduce fraud and chargebacks during e-commerce transactions. It allows Mastercard and Visa card issuers to provide an extra level of protection, by authenticating the customer’s identity at the point of sale, sometimes by prompting for a previously-agreed PIN and/or password.

When using a 3-D charge, the end user is directed to the card issuer's ACS (Access Control Server) to complete the transaction.

Find more information on the previous page and happy coding! 💻🌙

Reminder about Limits and 2FA

For purchases below the no-kyc-maximum purchase size, you do not need any KYC nor do you need google 2FA.

When you want to purchase amounts greater than than the no-kyc-maximum, you'll need to both enable 2FA and have the contact pass kyc.

For larger purchase amounts, you'll be required to pass in the 6 digit 2FA token to complete the charges.

To work through removing the 2FA requirement for your integration, please reach out to [email protected] Unless your KYC/AML is already very stringent, it is very unlikely we will approve waiving this requirement.

/v1/card/charge3d initiates the purchase flow

This route does NOT charge the user's card, or complete the purchase.

let jwtToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJlMzE3YjdlNy0yMzQ1LTQ0MWMtODA0Ni1kYjgxNTkyYmEyN2YiLCJzdXBlclVzZXIiOnRydWUsImNvbnRhY3QiOmZhbHNlLCJlbWFpbCI6ImRhbmllbEBjYXJib24ubW9uZXkiLCJpYXQiOjE1NTczMjc5MTR9.WZnSR5N1FebmT9nMu97PJvku49NY0jk4aKVPKm_1MlM';

let headers = {
  headers: {
    Authorization: `Bearer ${jwtToken}`
  }
};

let url = `${ROOT}/v1/card/charge3d`;

let data = {
  creditDebitId: "0f61c914-4f7e-48a1-9951-b8725638bf14",
  fiatChargeAmount: "1000",
  cryptocurrencySymbol: "eos",
  receiveAddress: "blockone1111",
  confirmationUrl: "www.mycallback.example",
  successRedirectUrl: "www.successURL.com",
  errorRedirectUrl: "www.errorURL.com",
  contactId:  "ab5bb41b-5979-4a54-b734-23eb9076188e"
}

axios.post(url, data, headers).then(result => console.log(result)).catch(err => console.log(err));
{ 
  message: 'Card enrolled in 3D Secure.',
  orderId: '3d7ee66f-9f25-4cf1-a38a-6e43c0e7ce15',
  charge3denrolled: 'Y',
  md: 'UEZOVVBqeE5SRDQ4VFVSSVBrbFhaRTQyZGtOblUwSllSV3B5YlRZeGJ6bHdlbWM5UFR3dlRVUklQanhoWTNOVmNtdythSFIwY0hNNkx5OTNaV0poY0hBdWMyVmpkWEpsZEhKaFpHbHVaeTV1WlhRdllXTnpMMjFoYzNSbGNtTmhjbVF1WTJkcFBDOWhZM05WY213K1BIQmhia3hsYm1kMGFENHhOand2Y0dGdVRHVnVaM1JvUGp4dFpYTnpZV2RsU1dRK1VFRlNaWEV0TVRVMk9UazNNemMxT0RVeU1pMHRNekF4TnpjeU1ETTVQQzl0WlhOellXZGxTV1ErUEM5TlJENDhMMU5VUGc9PTptZGZGaXRCR0pBdDlQWkZqMmJsRFAySE5kcncyYjN2MjJnTUZVTEVtcHhiYUgzQU4veERVNE9MMVZ3VmZFTlFMUDE=',
  pareq: 'eJxVUl1PwjAU/SvEd3bXjrGNXJr4GU2EGJQYfCvtjSy6bnadGf562wmKfbrn3O97ik87S3T1SKqzJHBBbStfaVTq+dnD+Yo+xiydFkWWZGmecj4eJzHLMh4nxZnAIUDgJ9m2rI1gURxxhCP0tazaSeMESvVxcbcU03SScIZwgFiRvbsS8f+H8EOjkRWJS2m3tRktakP7EcLAoao74+xe5HyKcATY2Xexc65pZwCtNHpb95Ea0qMqpMMnA2VJl86zGvxo9pUSDbIpVV017+QIIRRB+Jv8oQtW65v2pRbb61W/edPxYp/3z2WxXt8sly+mebz/2swRQgRq6UjwmBUsjtmIJ7NJMWM5wsCjrMK0gvGw5wFgE3qcn3pOGfTCWDLKrzvxrl+E1Dd+LR/hb/5ro6ZWidpqsr5nsBH+dri8DVoo58+bspObp2xQZXCEuqW/pxf6p3AACCEVDoLD4Zd469/v+QY8W7/8',
  termurl: 'https://sandbox.carbon.money/v1/creditcard/charge3d/apicomplete',
  acsurl: 'https://sandbox.carbon.money/v1/card/sandboxacs',
  code: 200 
}
{
  message: 'Card not enrolled in 3D Secure.',
  charge3denrolled: 'N',
  code: 200
}
{
  message: 'Unknown 3DS enrollment for this card (2 attempts).',
  charge3denrolled: 'U',
  code: 200
}

// 400; applies for any cryptocurrencySymbol passed in of course
{
  "message": "cryptocurrencySymbol not supported: doge. Refer to https://docs.carbon.money/docs/cryptocurrencies for list of valid symbols.",
  "status": 400
}


// 401
{
  "message": "The bitcoin address you specified is invalid or is not a legacy bitcoin address.  We currently do not support segwit, bech32, or other recent bitcoin address types.",
  "status": 401
}

// 404
{
  "message":"Card by contactId and creditDebitId not found.",
  "code":"404"
}

// 500
{
  "message":"Error getting card(s) by contactId and/or creditDebitId.",
  "code":"500"
}

// 403
{
  "message": "2FA is required for transactions greater in value than $250.",
  "status": 403
}

// 403
{
  "message": "You will exceed your remaining daily limit of $250. Please go through KYC to increase your daily limits to $2500.",
  "status": 403
}


// 403
{
  "message": "Your daily limit is exceeded with remaining $50 with a charge of $100. Please try transacting with a smaller amount. Also you can go through KYC to increase your daily limits to $2500.",
  "status": 403
}


// 429
// These blocked issuing countries are a subset of our blocked countries
// mentioned in https://docs.carbon.money/docs/blocked-countries. 
// Namely they include China (CN), Russia (RU), Vietnam (VN), Bolivia (BO),
// Colombia (CO), Ecuador (EC), Algeria (DZ),  Bangladesh (BD), Indonesia (ID),
// Jordan (JO), Kyrgyzstan (KG), Morocco (MA), Nepal (NP), Saudi Arabia (SA), 
// Iran (IR) , Pakistan (PK), Taiwan (TW), and Cambodia (KH).
// Note that we still block the missing countries from the subset-- 
// Cuba (CU), North Korea (KP), and the United States (US)-- by geolocation
// either due to licensing restrictions to sell crypto in the case of the US
// or international sanctions concerns in the case of CU and KP
{  
  "message": "Check 3D enrollment error with code 60110: we unfortunately cannot support the country from which your card is issued. Please try a different card.",
  "code": 429
}

// 412
{  
  "message": "Rates have changed. Please pull rates again and pass in the latest rateSignature.",
  "code": 412
}


// 429
// very, very rare but possible
{  
  "message": "Check 3D enrollment error with code 600036: our payments processor unfortunately is having some trouble processing your card. Please try a different card.",
  "code": 429
}


// 403
// In conjunction with geoblocking we block cards from some issuing
// countries from transacting without KYC for the associated contact. 
// These countries are: Chile, Costa Rica, Mexico, Guatamela, Argentina,
// Portugal, Ukraine, Colombia, Paraguay, Japan, Uruguay, Brazil, 
// Cayman Islands, Trinidad & Tobago, Peru, and South Africa.
// If the contact reaches out to [email protected], we can potentially
// clear that contact to transact. We have this blocking schedule in place given
// our determination of the threat of fraud from cards from these 
// issuing countries. We hope to soon greatly reduce this non-KYC
// blacklist as we further develop our anti-fraud system
{
  "message": "Our transaction was flagged by our anti-fraud system as high-risk. Please reach out to [email protected] to clear your account for transacting again.",
  "status": 403
}

// 403
// this is pretty rare but possible
{
  "message": "The transaction was flagged by our antifraud system as high risk. You can at most purchase up to $25 of crypto a transaction. To increase your limits please go through KYC. Your card was not charged. If you have any questions contact [email protected]",
  "status": 403
}

// 403 (not relevant if payment gateway integration)
{
  "message": "Insufficient treasury funds for btc. Contact Carbon for details. Your card has not been charged.",
  "status": 403
}

// 500
{  
  "message": "Error checking 3D enrollment.",
  "code": 500
}

// 500
{  
  "message": "Error charging credit/debit card.",
  "code": 500
}

Request Parameters

Parameters
Access
Description

creditDebitId

required

The creditDebitId created in the 'Adding a credit/debit card' endpoint above

fiatChargeAmount

required

The amount you'd like to charge a card. You need to pass in a string without any decimals. For example a charge of $10.39 would be rendered as "1039" and $233.91 would be "23391".

cryptocurrencySymbol

required

The symbol for the cryptocurrency the user would like to purchase. See here for the full list of supported cryptocurrencies and their symbols.

receiveAddress

required (if not payment gateway)

The public key at which you'd like to receive your order at. Make sure that this address is correct and verified or else you will not receive your order. We perform some basic blockchain address verification and plan to add more over time but please also implement on your end.

contactId

required

token

optional (required if 2FA is enabled for the contact)

the Google 2FA token

rateSignature

optional (not relevant if payment gateway)

Obtained from the response header under rateSignature of the API Request for current fiat & crypto exchange rates endpoint above. Remember that you can pass this in and the call will fail if the current 'rateSignature' does not match the provided 'rateSignature' (meaning rates have changed) as a way to effectively lock in rates for your users. If the 'rateSignature' has changed and the call fails, prompt your user to retry obtaining a quote and charging their credit/debit card.

memo

optional (not relevant if payment gateway)

An optional memo for the crypto purchase. Some on-chain transactions may accept memos such as Ripple, EOS, and Binance Chain. Some particular classes of on-chain transactions require memos such as exchange deposits.

confirmationUrl

required

After the user completes 3DS authentication at their bank's ACS server, we authorize the transaction, and then send crypto, you will receive a POST to your confirmation url with the order details set in the request body. The final credit/debit card charge and the crypto transfer is performed at our termination url. Note that we will always post to the confirmationUrl regardless of whether the transaction is a success or failure. More information available below. Again note that crypto will not be sent if you are a payment gateway and instead we will just post the relevant order details.

successRedirectUrl

required

After the user completes 3DS authentication at their bank's ACS server, the transaction is authorized, and we successfully send crypto, your page will be redirected from our terminal url to here. More information available below. Again note that crypto will not be sent if you are a payment gateway.

errorRedirectUrl

required

After the user completes 3DS authentication at their bank's ACS server but transaction processing otherwise fails (either at the credit/debit card charge side or the crypto transfer side), your page will be redirected from our termination url to here. More information available below. Again note that crypto will not be sent if you are a payment gateway.

Removing Confirmation URL Requirement Aside

If you are planning to implement a stateless fiat gateway, please reach out to [email protected] and we can configure your superuser to not require a confirmationUrl. We can attach the order ID as a query parameter to the {success,error}RedirectUrl and you can instead get the order's status and manage your UI/user flow accordingly. The query parameter will be called 'orderId'.

Payment Gateway Considerations

If you're integrating with us as a payment gateway, ignore the rateSignature, memo, receiveAddress, successRedirectUrl, errorRedirectUrl, and confirmationUrl fields specified above. Also refer to the following parameters below for specifying your quote for a purchase of crypto. Note that in the Payment Gateway service you can set your termination url (termUrl) after your user completes 3DS authentication on their credit/debit card. If you're not integrating with us a payment gateway, ignore the following parameters.

Request Parameters (Payment Gateway)

Parameters
Access
Description

paymentGateway

required (if payment gateway integration)

Boolean. Specify you are providing quote as the liquidity provider/market maker if value is true.

exchangeRateCrypto

required (if 'paymentGateway' is true)

The quote you will be providing for the 'cryptocurrencySymbol' purchase.

6. Charge 3D Response -> ACS Charge 3D Completion

Charge 3D Response Params Breakdown

Assuming "charge3denrolled" is 'Y':

  1. "md": Merchant data for referencing a 3DS authorization request. This data is not meant to be readable but instead act as an ID to link a 3DS request to a latter 3DS response from our side and your side as well.

  2. "pareq": Payer authentication request containing necessary information to authenticate the cardholder at their card issuer's ACS server. This data is not meant to be readable like 'md'.

  3. "termurl" Termination url where we process the ultimate 3DS response after users authenticate with their bank's ACS server.

  4. "acsurl" URL for issuing bank's ACS server at which ultimate 3DS authorization will occur.

Next steps...

The response to the /v1/card/charge3d api call will contain the necessary data to authenticate the customer on the Access Control Server (if the customer's card is enrolled).

Using the response to v1/card/charge3d, you will need to check the property charge3denrolled. If its value is anything other than 'Y', you must prompt the customer to try again with a different card as only 3DS charges are supported.

Sandbox ACS and TermUrl

When testing using our sandbox endpoints, the 'acsurl' will always be https://sandbox.carbon.money/v1/card/sandboxacs and the 'termurl' will always be `https://sandbox.carbon.money/v1/card/charge3d/complete'. By default, the transaction will be processed, unless you include 'errorResponse' in the POST request to the ACS, in which case the transaction will be declined. This should be used for testing your 'errorRedirectUrl' versus your 'successRedirectUrl'. In sandbox note that the ACS confirmation page client-side in our widget is skipped to streamline sandbox testing.

Form v API

Below we have a form example to demonstrate how building out a 3DS charge UI would look like. You can handle through API so long as you confirm to our specified 3DS request/response design and have your URLs configured as well. For example, consider this sample 3DS charge success response.

let resp = {
"charge3denrolled": "Y",
"md": "UEZOVVBqeE5SRDQ4VFVSSVBucE9abUp5WWtTlDR0NLaFVSZUV5aStFPQ==",
"pareq": "eJxVUmFPw9C12xiQowlCopiIqGji5Yqj25lPCwOutoQKHZzxqOQgF5soqzk=",
"termurl": "https://api.carbon.money/v1/creditcard/charge3d/complete",
"acsurl": "https://cardissuerurl.com"
};

let pareq = resp['pareq'];
let md = resp['md'];
let termurl = resp['termurl'];

Since the card is 3DS enrolled, we extract the 'md',' pareq', and 'termurl' to post to the 'acsurl'.

const axios = require('axios');

// note that the request params are case-sensitive
let data = {
'PaReq': pareq,
'MD': 'md,
'TermUrl': termurl
};
axios.post(acsurl, data);

This post to the ACS url should forward the user to their card issuer's ACS for authentication so long as Javascript is enabled in your client's environment. Otherwise make sure to include a message to your users to enable Javascript in their client environment if applicable. There the user will usually enter in a security code or password with their bank but sometimes if the issuing bank already has some authorization token (say from an account sign-in) stored in the client-side environment so there may be an auto-redirect. Note that we auto-redirect in sandbox. Issuing banks may also decide to bypass explicit 3DS authentication at their discretion but generally they are still on the hook for chargebacks :).

Lastly, there will be always be a post to your 'confirmationUrl' after the ACS processes the 3DS authorization request from our terminal url as we process the transaction and a redirect to your 'successRedirectUrl' or 'errorRedirectUrl' depending upon whether the credit/debit charge authorization and crypto transfer (if not integrating as a payment gateway) was successful or not.

Note that our sandbox acs url will also take on the role of our backend termination url in sandbox.

When redirected to the errorRedirectUrl, the URL will include a comma-delimited list of error codes as a query parameter, 'errors' .

Error Codes
Description

1

CVC error

2

Postal code error

3

Billing address error

4

Other incorrect field(s)

5

Transaction declined by bank.

6

Only Visa or Mastercard accepted right now

7

3DS authentication failure

8

3DS authentication could not be performed

9

3DS authentication was performed but not completed by user's bank

ACS Window Styling

The size of the ACS page displayed in the customer’s browser in determined by their card's issuing bank. Note that Visa and Mastercard both recommend that the Verified by Visa authentication window should be 390×400 pixels in size.

ACS UI Form Example

if (response.charge3denrolled !== 'Y' && response.errorcode !== 0) {
  //  require a different card
} else {
  const url = getGeneratedPageURL({
    html: `
      <div>
        <h2 style="text-align:center;">Loading ACS Page...</h2>
        <form style="visibility:hidden;" name="form" id="form" action=${response.acsurl} method="POST">
          <input type="hidden" name="PaReq" value=${response.pareq} />
          <input type="hidden" name="TermUrl" value=${response.termurl} />
          <input type="hidden" name="MD" value=${response.md} /> 
          <input type="hidden" name="errorResponse" value=${true} /> // OPTIONAL
        </form>
      </div>
    `,
  });
}

// Sample of function getGeneratedPageURL

function getGeneratedPageURL({ html, css, js }) {
  const getBlobURL = (code, type) => {
    const blob = new Blob([code], { type });
    return URL.createObjectURL(blob);
  };

  const cssURL = getBlobURL(css, 'text/css');
  const jsURL = getBlobURL(js, 'text/javascript');

  const source = `
    <html>
      <head>
        ${css && `<link rel="stylesheet" type="text/css" href="${cssURL}" />`}
        ${js && `<script src="${jsURL}"></script>`}
      </head>
      <body onLoad="document.form.submit();">
        ${html || ''}
      </body>
    </html>
  `;

  return getBlobURL(source, 'text/html');
}

Next steps...

Once 3DS authentication is complete, a call is made to our backend termination url to complete the transaction, send the result to your confirmationUrl, and finally redirects to your successRedirectUrl or errorRedirectUrl depending on the 3DS authorization result. Note that our sandbox acs url will also take on the role of our backend termination url in sandbox.

Example 3DS Charge Completion Success & Error Response

{
  "success": true, 
  "message": "successful order cefa4acb-e944-491b-acbd-46f09d2f4d70!",
  "details": {
    "uuid": "cefa4acb-e944-491b-acbd-46f09d2f4d70",
    "baseamount": "1409",
    "currency": "EUR",
    "maskedpan": "340000#####0611",
    "transactionstartedtimestamp": "2019-05-21 22:27:18",
    "quantityCryptoPurchased": "2.49594778",
    "cryptocurrency": "EOS",
    "cryptoExchangeRateUSD": "6.3011",
    "fiatExchangeRate": "0.1141273592417303 EUR/HKD",
    "receiveAddress": "carbon12labs",
    "transactionHash": "7411292f651b603de345223ce9108d8257c9cda55cc9a3914b1eae2bd29c3b6c"
    }
}

// default sandbox acs error message
{
  "success": false,
  "message": "'Error processing transaction."
}

// some of the following error messages/codes may be combined
// we also provide the linked error codes passed in
// under the 'errors' query param attached to the 'errorRedirectUrl'
// we try but cannot guarantee all error messages will be as expected

// error code 1
{
  "success": false,
  "message": "Incorrect CVC"
}

// error code 2
{
  "success": false,
  "message": "Incorrect Postal Code"
}

// error code 3
{
  "success": false,
  "message": "Incorrect Billing Address"
}

// error code 4
// we do not know the list of incorrect fields beforehand
// and so cannot provide an error code at this time
{
  "success": false,
  "message": "Incorrect Field(s): card number"
}

// error code 5
// decline message from issuing bank 
{
  "success": false,
  "message": "Transaction was declined by your bank with the following message: Transaction not permitted on card. Please reach out to them to authorize the charge if possible. You may also need to verify you are entering in the correct card data."
}

// error code 6
// we do not know the list of incorrect fields beforehand
// and so cannot provide an error code at this time
{
  "success": false,
  "message": "Only Visa and Mastercard accepted right now."
}


// error code 7
// user failed 3ds authentication
// in our experience this may be because they entered in incorrect credentials
// with their bank or did not complete in time
{
  "success": false,
  "message": "3DS authentication failed. Please make sure you are entering in the right credentials with your bank."
}

ACS Confirmation Page

Thank you for your feedback

What's Next

Order Status

Credit/Debit Purchases API


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.