1C-Bitrix Integration with Nova Poshta Delivery Service (Ukraine)
Nova Poshta is the dominant delivery service in the Ukrainian e-commerce market. Coverage spans virtually every locality in Ukraine, with its own branch network and courier delivery. The Nova Poshta API (version 2.0) is JSON-over-HTTP with API key authorization. Documentation is available in Ukrainian and Russian, and is well-structured.
Nova Poshta API Principles
All requests are POST to https://api.novaposhta.ua/v2.0/json/. Authorization is via the apiKey field in the body of each request. The request structure is uniform:
{
"apiKey": "your_api_key",
"modelName": "TrackingDocument",
"calledMethod": "getStatusDocuments",
"methodProperties": {}
}
This is an atypical REST architecture — a single endpoint for all methods, with the method passed as the calledMethod field. However, it wraps neatly into a single PHP method:
private function apiCall(string $model, string $method, array $props = []): array
{
$payload = [
'apiKey' => $this->apiKey,
'modelName' => $model,
'calledMethod' => $method,
'methodProperties' => $props,
];
$ch = curl_init('https://api.novaposhta.ua/v2.0/json/');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
if (!$response['success']) {
throw new \RuntimeException(implode(', ', $response['errors'] ?? ['Unknown error']));
}
return $response['data'];
}
Delivery Cost Calculation
public function calcCost(string $citySender, string $cityRecipient, float $weight): float
{
$data = $this->apiCall('InternetDocument', 'getDocumentPrice', [
'CitySender' => $citySender, // sender city ref
'CityRecipient' => $cityRecipient, // recipient city ref
'ServiceType' => 'WarehouseDoors', // branch-to-door
'Weight' => $weight,
'Cost' => 0, // declared value
'CargoType' => 'Cargo',
'SeatsAmount' => 1,
]);
return (float)($data[0]['Cost'] ?? 0);
}
Delivery types (ServiceType): WarehouseWarehouse (branch-to-branch), WarehouseDoors (branch-to-door), DoorsDoors (door-to-door), DoorsWarehouse (door-to-branch). Pricing differs between types.
City and Branch Search by Keyword
public function findCity(string $query): array
{
return $this->apiCall('Address', 'searchSettlements', [
'CityName' => $query,
'Limit' => 5,
'Page' => 1,
]);
}
public function findWarehouse(string $cityRef, ?string $query = null): array
{
return $this->apiCall('AddressGeneral', 'getWarehouses', [
'CityRef' => $cityRef,
'FindByString' => $query,
'Limit' => 50,
]);
}
A dynamic search is implemented on the website: the customer enters a city name — searchSettlements is called; the customer selects a city — branches are loaded via getWarehouses. Results are cached for 1–4 hours.
Creating a Waybill (TTN)
public function createDocument(\Bitrix\Sale\Shipment $shipment): string
{
$order = $shipment->getOrder();
$props = $order->getPropertyCollection();
$data = $this->apiCall('InternetDocument', 'save', [
'PayerType' => 'Recipient', // recipient pays
'PaymentMethod' => 'Cash',
'DateTime' => date('d.m.Y'),
'CargoType' => 'Cargo',
'VolumeGeneral' => 0.001,
'Weight' => max($this->getWeight($shipment), 0.1),
'ServiceType' => 'WarehouseWarehouse',
'SeatsAmount' => 1,
'Description' => 'Goods',
'Cost' => $order->getPrice(),
'CitySender' => $this->getOption('SENDER_CITY_REF'),
'Sender' => $this->getOption('SENDER_REF'),
'SenderAddress' => $this->getOption('SENDER_WAREHOUSE_REF'),
'ContactSender' => $this->getOption('CONTACT_REF'),
'SendersPhone' => $this->getOption('SENDER_PHONE'),
'CityRecipient' => $props->getItemByOrderPropertyCode('NP_CITY_REF')?->getValue(),
'RecipientAddress' => $props->getItemByOrderPropertyCode('NP_WAREHOUSE_REF')?->getValue(),
'RecipientsPhone'=> $props->getItemByOrderPropertyCode('PHONE')?->getValue(),
'RecipientName' => $props->getItemByOrderPropertyCode('FIO')?->getValue(),
'Recipient' => $this->createOrGetRecipient($props),
]);
$intDocNumber = $data[0]['IntDocNumber'] ?? ''; // TTN
$props->getItemByOrderPropertyCode('NP_TTN')?->setValue($intDocNumber);
$order->save();
return $intDocNumber;
}
The Sender, SenderAddress, and ContactSender parameters are ref identifiers of objects in the Nova Poshta system. They are obtained once during integration setup via the Counterparty/getCounterparties method.
TTN Tracking
public function trackDocument(string $ttn): array
{
$data = $this->apiCall('TrackingDocument', 'getStatusDocuments', [
'Documents' => [['DocumentNumber' => $ttn]],
]);
$doc = $data[0] ?? [];
return [
'status' => $doc['Status'] ?? '',
'warehouseTo' => $doc['WarehouseRecipient'] ?? '',
'scheduledDate' => $doc['ScheduledDeliveryDate'] ?? '',
];
}
Nova Poshta does not provide push webhooks for status changes. Polling via agent every 2–3 hours for active TTNs.
Timelines
| Scope | Timeline |
|---|---|
| Calculation + city/branch search + widget | 4–5 days |
| + TTN creation + tracking | +2 days |
| + Auto-TTN creation on status change | +1 day |







