Integrate your external CRM system with the B2COPY API
Learn how to authorize in B2COPY from your external CRM system and obtain the access token required for using the B2COPY API
This guide explains how to integrate your external CRM system with B2COPY to authorize in B2COPY, obtain an access token for API requests, and initiate fee processing between master and investment accounts.
Prerequisites
Create a service admin in the Admin panel, on whose behalf your external CRM system will make API requests to B2COPY.
Configure your external CRM system to generate signed JWT tokens. Each token must include the following claims:
admin_id— the identifier of the created service admin for whom the JWT token is issued.device_ip— the IP address from which the request is made.key_id— the identifier of the private key.
Generate keys
Run the following OpenSSL commands to generate an EC (Elliptic Curve) private and public key pair for signing and verifying JWT tokens:
# Generate private key
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 \
-out ec_private_pkcs8.pem
# Generate public key
openssl pkey -in ec_private_pkcs8.pem \
-pubout -out ec_public.pemSecurity note
Keep your private key secure and never share it.
Provide the public key to the B2COPY support team. It will be used to verify signed JWT tokens.
Contact B2COPY support
To complete the integration, provide the following to the B2COPY support team:
Your public key (ec_public.pem).
The unique key identifier.
The IP addresses from which requests to the B2COPY API will be made.
The B2COPY support team will register your integration, configure your public key in the system, and provide further instructions to enable API authorization.
Generate JWT tokens
Use the following Java code to generate and sign JWT tokens with your private key. The token includes the admin_id and device_ip claims and is signed using the ES256 algorithm.
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.SignatureAlgorithm;
import java.security.Key;
import java.time.Instant;
import java.util.Date;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
public class JwtIssuer {
/**
* Load EC private key from PKCS8 PEM file
*/
static PrivateKey loadEcPrivatePkcs8(String pemPath)
throws Exception {
String pem = Files.readString(Path.of(pemPath));
String base64 = pem
.replaceAll("-----\\w+ PRIVATE KEY-----", "")
.replaceAll("\\s", "");
byte[] der = Base64.getDecoder().decode(base64);
return KeyFactory
.getInstance("EC")
.generatePrivate(new PKCS8EncodedKeySpec(der));
}
/**
* Issue JWT token for external system authentication
*
* @param adminId Administrator ID for token
* @param deviceIp IP address making the call
* @param privPemPath Path to private key file
* @return Signed JWT token
*/
public static String issue(
String adminId,
String deviceIp,
String privPemPath) throws Exception {
var priv = loadEcPrivatePkcs8(privPemPath);
return Jwts.builder()
.setIssuer("your-service")
.setIssuedAt(Date.from(Instant.now()))
.setExpiration(Date.from(Instant.now().plusSeconds(1800)))
.claim("admin_id", adminId)
.claim("device_ip", deviceIp)
.signWith(priv, SignatureAlgorithm.ES256)
.compact();
}
public static void main(String[] args) throws Exception {
String token = issue(
"42",
"203.0.113.10",
"/path/to/ec_private_pkcs8.pem"
);
System.out.println(token);
}
}Obtain B2COPY access and refresh tokens
To obtain a pair of access and refresh tokens from B2COPY, use the following method:
POST [host]/api/admin/auth/v2/admin/adminservice/authexternalcrm
Request
Body parameters
external_token
string
Yes
The generated JWT token.
Request example
curl -X POST "https://demo-standalone-v2.prod.b2copy.tech/api/admin/auth/v2/admin/adminservice/authexternalcrm" \
-d '{
"external_token": "<your-generated-jwt-token>"
}'
Response
The response returns access and refresh tokens for the service admin specified in the token claims. All other API requests to B2COPY must include the obtained access token in the Authorization header.
Response example
{
"accessToken": "<access-token>",
"refreshToken": "<refresh-token>"
}Refresh the access token
To refresh the expired access token, use the following method:
POST [host]/api/admin/auth/v2/admin/adminservice/refreshtoken
Request
Request example
curl -X POST "https://demo-standalone-v2.prod.b2copy.tech/api/admin/auth/v2/admin/adminservice/refreshtoken" \
-H "Authorization: Bearer <refreshToken>"Response
The response returns a new pair of access and refresh tokens.
Initiate fee processing between master and investment accounts
To initiate fee calculation and deduction from investment accounts subscribed to a specific master account, use the following method:
POST [host]/api/admin/fees/v2/fee/feeservice/processext
Request
Body parameters
account_login
string
Yes
The account login (number).
request_id
string
Yes
The identifier of a withdrawal request initiated in the external CRM system.
Key points about request_id
The request_id represents the withdrawal operation ID in your external CRM system. It's used is to link a withdrawal request in the CRM with fee processing in B2COPY.
How it works:
A user initiates a withdrawal request in your CRM.
The CRM generates a unique ID for this withdrawal.
You pass this ID as
request_idto the B2COPY API method.Use the same
request_idin subsequent polling requests to track the fee payment status for this withdrawal.
This ensures a direct and reliable connection between the withdrawal in your CRM and its corresponding fee processing in B2COPY.
Request example
curl -X POST "https://demo-standalone-v2.prod.b2copy.tech/api/admin/fees/v2/fee/feeservice/processext" \
-H "Authorization: Bearer <your-access-token>" \
-d '{
"account_login": "123456",
"request_id": "withdrawal_req_789"
}'Response
The response returns the status of the initiated fee processing.
Response example
{
"account_login": "123456",
"request_id": "withdrawal_req_789",
"status": 1
}Possible statuses:
1 —
FEE_PROCESS_STATUS_PENDING: the request is being processed (temporary status).Fee payments from investment accounts to the master account are in progress. After receiving this status, start monitoring by using polling.
2 —
FEE_PROCESS_STATUS_SUCCESS: the request completed successfully (final status).All fees have been paid from investment accounts to the master account. No further action is required.
3 —
FEE_PROCESS_STATUS_ERROR: an error occurred during request processing (final status).Check the error details and retry if necessary.
Polling implementation
The FEE_PROCESS_STATUS_PENDING status is temporary and indicates that B2COPY is actively processing fee payments between accounts. Implement polling as follows:
After make the initial request with
account_loginandrequest_idand receiving theFEE_PROCESS_STATUS_PENDINGstatus, wait 3-5 seconds.Repeat the request with the same
request_id.Continue polling until you receive
FEE_PROCESS_STATUS_SUCCESSorFEE_PROCESS_STATUS_ERROR.
Polling example
/**
* Poll fee processing status for a withdrawal operation
*
* @param accountLogin Account identifier
* @param withdrawalId Withdrawal operation ID from your CRM
* @return Final status (SUCCESS or ERROR)
*/
public FeeProcessStatus pollFeeProcessing(
String accountLogin,
String withdrawalId) throws Exception {
while (true) {
// Make API call with your CRM withdrawal ID
FeeProcessResponse response = feeService.processExt(
accountLogin,
withdrawalId // Your CRM withdrawal operation ID
);
if (response.getStatus() == FeeProcessStatus.SUCCESS) {
// Fee processing completed - safe to proceed with withdrawal
return FeeProcessStatus.SUCCESS;
}
if (response.getStatus() == FeeProcessStatus.ERROR) {
// Error occurred - handle accordingly in your CRM
throw new FeeProcessingException(
"Fee processing failed for withdrawal " + withdrawalId
);
}
if (response.getStatus() == FeeProcessStatus.PENDING) {
// Fees are being paid from investors to master
// Wait and retry with the same withdrawal ID
Thread.sleep(3000); // Wait 3 seconds
continue;
}
}
}Last updated
Was this helpful?

