1C-Bitrix Integration with BelVEB Internet Acquiring (Belarus)
BelVEB (Belarusian Bank for Development and Reconstruction) provides internet acquiring for Belarusian online stores. The bank is focused on the corporate segment, and its payment gateway has a number of technical characteristics that distinguish it from more common solutions. Integration with 1C-Bitrix requires developing a custom payment system handler — ready-made modules on the Marketplace are practically absent.
Gateway Technical Parameters
BelVEB provides a payment gateway based on 3DS v2 (EMV 3-D Secure) technology. API — REST/JSON, authentication via a Bearer token obtained through a separate request to /oauth/token.
Main endpoints (test environment: test-api.belveb.by, production: api.belveb.by):
POST /v1/payments — create a payment
GET /v1/payments/{id} — payment status
POST /v1/payments/{id}/capture — capture (two-stage)
POST /v1/payments/{id}/cancel — cancel
POST /v1/refunds — refund
Obtaining an Access Token
class BelvebAuth
{
private const TOKEN_URL = 'https://api.belveb.by/oauth/token';
public function getToken(string $clientId, string $clientSecret): string
{
$ch = curl_init(self::TOKEN_URL);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'grant_type' => 'client_credentials',
'client_id' => $clientId,
'client_secret' => $clientSecret,
'scope' => 'payments',
]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'],
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
return $result['access_token'] ?? throw new \RuntimeException('Token request failed');
}
}
The token has a limited TTL (typically 1 hour). Cache it in \Bitrix\Main\Data\Cache with TTL minus 60 seconds — otherwise the token may expire mid-processing.
Creating a Payment
public function createPayment(array $data, string $token): array
{
$payload = [
'amount' => [
'value' => number_format($data['amount'], 2, '.', ''),
'currency' => 'BYN',
],
'description' => 'Order #' . $data['orderNumber'],
'orderId' => $data['orderNumber'],
'returnUrl' => $data['returnUrl'],
'cancelUrl' => $data['cancelUrl'],
'notifyUrl' => $data['notifyUrl'],
'capture' => true,
'customer' => [
'email' => $data['customerEmail'],
],
];
$ch = curl_init('https://api.belveb.by/v1/payments');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $token,
'X-Idempotency-Key: ' . $data['idempotencyKey'],
],
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
return $result;
}
The X-Idempotency-Key field — a UUID string, unique for each payment creation attempt. Ensures idempotency: a repeated request with the same key returns the same payment without creating a duplicate.
Webhook Notifications
BelVEB signs notifications with HMAC-SHA256. The signing key is issued during onboarding.
public function verifyWebhook(string $body, string $signature, string $secret): bool
{
$computed = base64_encode(hash_hmac('sha256', $body, $secret, true));
return hash_equals($computed, $signature);
}
// In the handler:
$body = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
if (!$gateway->verifyWebhook($body, $signature, $webhookSecret)) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
// Handle only succeeded
if ($event['type'] === 'payment.succeeded') {
$payment->setPaid('Y');
$payment->save();
}
Payment statuses:
| Status | Meaning |
|---|---|
pending |
Created, awaiting payment |
processing |
Processing |
succeeded |
Successfully paid |
failed |
Rejected |
cancelled |
Cancelled |
refunded |
Refunded |
Belarus-Specific Notes
BelVEB supports payment by VISA, Mastercard, and Belkart cards. For Belkart cards, 3DS verification works through the national authentication system — it is important to test this scenario separately.
Currency — BYN; amount is transmitted in rubles with two decimal places (not in kopecks, unlike some other gateways). This is a frequent source of errors when porting code from other integrations.
Timeline
| Task | Duration |
|---|---|
| Handler development + OAuth authorization | 2–3 days |
| Full cycle testing (payment, refund, webhook) | 1 day |
| Live connection | 1 day |







