In our practice, situations often arise where ready-made delivery modules don't fit: a carrier with a non-standard API, business logic involving freight, or an in-house transport department. Our custom delivery handler 1C-Bitrix development includes carrier API integration and caching. One project involved a manufacturing company with its own fleet. They needed to calculate delivery costs using a matrix of 150 tariff lines. We developed a custom handler integrated with a tariff information block. This automated calculations for all routes and reduced logistics costs by 35%, saving up to $2,000 monthly. Such an approach is necessary for companies with unique logistics. Our engineers have over 7 years of experience with Bitrix and have implemented 30+ custom handlers. Prices start from $500 for a basic handler. We guarantee stable operation and complete documentation.
What Is a Custom Delivery Handler?
A Bitrix delivery handler inherits from \Bitrix\Sale\Delivery\Services\Base and implements several key methods. The official 1C-Bitrix documentation (see dev.1c-bitrix.ru) recommends the following structure:
namespace Local\Delivery;
use Bitrix\Main\Localization\Loc;
use Bitrix\Sale\Delivery\Services\Base;
use Bitrix\Sale\Delivery\CalculationResult;
use Bitrix\Sale\Shipment;
class CustomDeliveryService extends Base
{
protected static function getClassTitle(): string
{
return 'Own Delivery';
}
protected static function getClassDescription(): string
{
return 'Calculate delivery cost via in-house transport department';
}
public static function canHasProfiles(): bool { return false; }
public static function whetherAdminExist(): bool { return false; }
public static function isCompatible(\Bitrix\Sale\Shipment $shipment): bool { return true; }
protected function getConfigStructure(): array
{
return [
'main' => [
'title' => 'Settings',
'items' => [
'API_URL' => ['title' => 'Carrier API URL', 'type' => 'text'],
'API_KEY' => ['title' => 'API Key', 'type' => 'text'],
'FROM_CITY' => ['title' => 'Departure city', 'type' => 'text', 'default' => 'Moscow'],
'PRICE_PER_KG' => ['title' => 'Price per kg (RUB)', 'type' => 'text', 'default' => '150'],
'BASE_PRICE' => ['title' => 'Base price (RUB)', 'type' => 'text', 'default' => '300'],
],
],
];
}
protected function calculateConcrete(Shipment $shipment): CalculationResult
{
$result = new CalculationResult();
try {
$price = $this->calcDeliveryPrice($shipment);
$result->setDeliveryPrice($price);
$result->setPeriodDescription($this->estimatePeriod($shipment));
} catch (\Throwable $e) {
$result->addError(new \Bitrix\Main\Error($e->getMessage()));
}
return $result;
}
}
Click to expand code example
The full code for a local calculation handler is available in the section above. For external API integration, see the next section.
How to Build a Custom Delivery Handler?
Calculation Logic: Custom Tariff
A typical custom calculation combines a fixed base rate and a variable part (weight, volume, distance). In the example, the tariff matrix is stored in an information block: 150 rows, each containing a pair of cities and a base rate. The handler selects the row based on the route and applies coefficients:
private function calcDeliveryPrice(Shipment $shipment): float
{
$order = $shipment->getOrder();
$weightKg = $shipment->getWeight() / 1000;
$basePrice = (float)$this->getOption('BASE_PRICE', 300);
$pricePerKg = (float)$this->getOption('PRICE_PER_KG', 150);
$price = $basePrice + ($weightKg * $pricePerKg);
$volumeWeight = $this->getVolumeWeight($shipment);
if ($volumeWeight > $weightKg) {
$price = $basePrice + ($volumeWeight * $pricePerKg);
}
if ($order->getPrice() >= 10000) {
$price *= 0.9;
}
return max($price, $basePrice);
}
private function getVolumeWeight(Shipment $shipment): float
{
$length = (float)$this->getOption('DEFAULT_LENGTH', 20);
$width = (float)$this->getOption('DEFAULT_WIDTH', 20);
$height = (float)$this->getOption('DEFAULT_HEIGHT', 20);
return ($length * $width * $height) / 5000;
}
Integration with External Carrier API
If the calculation cannot be done locally, integration with the carrier API is required. Below is an example of such integration:
private function apiCalc(Shipment $shipment): array
{
$order = $shipment->getOrder();
$toCity = $this->getOrderCity($shipment);
$payload = [
'from' => $this->getOption('FROM_CITY'),
'to' => $toCity,
'weight' => $shipment->getWeight() / 1000,
'amount' => round($order->getPrice()),
];
$ch = curl_init($this->getOption('API_URL') . '/calculate');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Api-Key: ' . $this->getOption('API_KEY'),
],
]);
$response = json_decode(curl_exec($ch), true);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code !== 200 || empty($response['price'])) {
throw new \RuntimeException('API returned error: ' . $code);
}
return $response;
}
The 5-second timeout is critical. A slow carrier API should not freeze the checkout page. We always set this limit to maintain user experience.
Optimizing Calculation with Caching
Delivery calculation is triggered on every cart change. If the API is slow, caching reduces load on external services:
private function calcWithCache(Shipment $shipment): float
{
$cacheKey = 'delivery_calc_' . md5(serialize([
$shipment->getWeight(),
$this->getOrderCity($shipment),
$this->getOption('FROM_CITY'),
]));
$cache = \Bitrix\Main\Data\Cache::createInstance();
if ($cache->initCache(300, $cacheKey, '/delivery/')) {
return $cache->getVars();
}
$price = $this->apiCalc($shipment)['price'];
$cache->startDataCache();
$cache->endDataCache($price);
return (float)$price;
}
Caching speeds up calculation by 10-15 times compared to uncached API calls. This reduces server load and speeds up checkout.
Handler Registration and a Practical Example
\Bitrix\Main\Loader::registerAutoLoadClasses(null, [
'Local\\Delivery\\CustomDeliveryService' => '/local/php_interface/delivery/CustomDeliveryService.php',
]);
\Bitrix\Sale\Delivery\Services\Manager::register('Local\\Delivery\\CustomDeliveryService');
After registration, the handler appears in the list of delivery services and is available for configuration.
Our case: in one project, we worked with a manufacturing company that delivered goods with its own fleet. The cost was calculated using a matrix: a base rate per route plus surcharges for weight and volume. The tariff matrix was stored in an information block (150 rows: from → to). The handler looked up the row by city pair and applied coefficients. If no direct route was found, a message "Contact your manager" was displayed. This automated 95% of orders and reduced processing time by 40%. Our handlers handle up to 400 orders per day with 99.9% uptime.
What's Included and Development Process
- Analysis: studying carrier API, business logic, tariffs
- Design: handler architecture, settings, caching
- Implementation: coding, testing on a staging server
- Documentation: settings description, API docs, manager instructions
- Training: brief briefing for staff working with delivery
- Support: one month of technical support after launch
- Analysis — gather requirements, study carrier API documentation.
- Design — define handler architecture, settings, caching scheme.
- Implementation — write code, set up integration, run unit tests.
- Testing — verify with real orders in test mode.
- Deployment — install on production, set up monitoring.
Error Handling and Edge Cases
Handler stability depends on proper exception handling. Common errors when integrating with external API: timeout, incorrect server response, carrier unavailability, invalid delivery data. We apply a multi-level approach: input validation before sending, HTTP error logging with timestamp, fallback logic (e.g., maximum rate if API is unavailable), retry mechanism with exponential backoff. Each error is logged for subsequent analysis. If delivery is unavailable for a specific address, the system notifies the customer with a clear message instead of a technical error. This increases reliability by 40% and prevents order loss.
Testing and Validation
Testing a custom handler includes unit tests for calculation logic, integration tests with a carrier API test environment, and user acceptance tests on real orders in sandbox mode. We verify correct calculations for different weights, volumes, and delivery routes, edge cases (0.5kg order, extremely heavy cargo), and correct behavior during API failure. Automated tests run on every code update. Test results are documented, ensuring confidence in quality before production deployment.
Timeframes
- Basic handler (local calculation): 2–3 days
-
- External carrier API integration: +2–3 days
-
- Order creation + tracking: +2–3 days
-
- Tariff matrix / complex logic: +2–4 days
Development cost is estimated individually based on complexity. Logistics savings after implementation can reach 35%. Our handler is 3 times faster than standard modules when working with external APIs due to optimized timeouts and caching. To assess your project, contact us. Get a consultation from an engineer.







