How to Set Up a Delivery Service for CRM
Who can execute methods: administrator
Choose a tool for developing with an AI agent:
- use Alaio Vibecode to build an app for Bitrix24 from a task description without knowing any programming language. The agent writes the code and deploys the app to a server, with no manual hosting setup
- use the MCP server to develop a REST API integration in your own project. The agent refers to the official REST documentation
External delivery services can be connected to Bitrix24. This allows a manager to work with the delivery service within CRM cards: calculating costs and tracking status.
As a result of the scenario, a delivery service with profiles, shipment address properties, and an extra service will appear in CRM.
The scenario consists of five steps.
- Register a delivery handler using the sale.delivery.handler.add method
- Create the parent service and profiles using the sale.delivery.add method
- Add shipment properties for addresses using the sale.shipmentproperty.add method
- Link properties to delivery profiles using the sale.propertyRelation.add method
- Connect an extra service using the sale.delivery.extra.service.add method
Before You Start
Prepare the values used in the examples.
- an inbound webhook or OAuth token of a user with administrator permissions
- public HTTPS handler URLs:
CALCULATE_URL,CREATE_DELIVERY_REQUEST_URL,CANCEL_DELIVERY_REQUEST_URL - a unique delivery handler code, for example
uber - the payer type identifier
personTypeId. You can retrieve the list of types using the sale.persontype.list method - the property group identifier
propsGroupId. You can retrieve the list of groups using the sale.propertygroup.list method
1. Create a Delivery Handler
Register a handler using sale.delivery.handler.add. Pass four parameters to the method.
-
CODE— the symbolic code of the delivery service handler. For example, specifyuber. -
NAME— the name of the delivery service handler. PassUber. -
SETTINGS— an object containing information about the handler settings.-
CALCULATE_URL— the URL for calculating the delivery price, for examplehttps://gateway.bx/calculate.php. -
CREATE_DELIVERY_REQUEST_URL— the URL for processing the delivery. Specifyhttps://gateway.bx/create_delivery_request.php. -
CANCEL_DELIVERY_REQUEST_URL— the URL for canceling the delivery, for examplehttps://gateway.bx/cancel_delivery_request.php. -
HAS_CALLBACK_TRACKING_SUPPORT— an indicator of whether the service will send notifications. SetY. Notifications can be created using sale.delivery.request.sendmessage. -
CONFIG— a list of configurations. SpecifyMY_FIRST_SETTINGandMY_SECOND_SETTINGwith typeSTRING.
-
-
PROFILES— an array of delivery profiles. The handler must have at least one profile. SetTaxiandCargo.
The delivery service must receive a request at the specified URLs, process it, and return a response in the format expected by the CRM.
For more details on the request and response format, see the Webhooks for Working with Deliveries section.
How to Use Examples in Documentation
import { B24Hook } from '@bitrix24/b24jssdk'
const $b24 = B24Hook.fromWebhookUrl(process.env.B24_HOOK)
// B24_HOOK = 'https://your-domain.bitrix24.com/rest/USER_ID/TOKEN/'
const response = await $b24.actions.v2.call.make({
method: 'sale.delivery.handler.add',
params: {
CODE: "uber",
NAME: "Uber",
SETTINGS: {
CALCULATE_URL: "https://gateway.bx/calculate.php",
CREATE_DELIVERY_REQUEST_URL: "https://gateway.bx/create_delivery_request.php",
CANCEL_DELIVERY_REQUEST_URL: "https://gateway.bx/cancel_delivery_request.php",
HAS_CALLBACK_TRACKING_SUPPORT: "Y",
CONFIG: [
{
TYPE: "STRING",
CODE: "MY_FIRST_SETTING",
NAME: "My first setting",
},
{
TYPE: "STRING",
CODE: "MY_SECOND_SETTING",
NAME: "My second setting",
},
],
},
PROFILES: [
{
NAME: "Taxi",
CODE: "TAXI",
DESCRIPTION: "Taxi Delivery",
},
{
NAME: "Cargo",
CODE: "CARGO",
DESCRIPTION: "Cargo Delivery",
},
],
},
requestId: 'delivery-handler-add'
})
if (response.isSuccess) {
console.info(response.getData().result)
} else {
console.error(response.getErrorMessages().join('; '))
}
from b24pysdk import BitrixWebhook, Client
from b24pysdk.errors import BitrixAPIError
token = BitrixWebhook(
domain="your-domain.bitrix24.com",
webhook_token="user_id/webhook_key",
)
client = Client(token)
try:
response = client.sale.delivery.handler.add(
code="uber",
name="Uber",
settings={
"CALCULATE_URL": "https://gateway.bx/calculate.php",
"CREATE_DELIVERY_REQUEST_URL": "https://gateway.bx/create_delivery_request.php",
"CANCEL_DELIVERY_REQUEST_URL": "https://gateway.bx/cancel_delivery_request.php",
"HAS_CALLBACK_TRACKING_SUPPORT": "Y",
"CONFIG": [
{
"TYPE": "STRING",
"CODE": "MY_FIRST_SETTING",
"NAME": "My first setting",
},
{
"TYPE": "STRING",
"CODE": "MY_SECOND_SETTING",
"NAME": "My second setting",
},
],
},
profiles=[
{
"NAME": "Taxi",
"CODE": "TAXI",
"DESCRIPTION": "Taxi Delivery",
},
{
"NAME": "Cargo",
"CODE": "CARGO",
"DESCRIPTION": "Cargo Delivery",
},
],
).response
print(response.result)
except BitrixAPIError as error:
print(error)
<?php
// composer require bitrix24/b24phpsdk:"^3.0"
require_once 'vendor/autoload.php';
use Bitrix24\SDK\Services\ServiceBuilderFactory;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$log = new Logger('b24');
$log->pushHandler(new StreamHandler('php://stdout'));
$sb = (new ServiceBuilderFactory(new EventDispatcher(), $log))
->initFromWebhook('https://your-domain.bitrix24.com/rest/USER_ID/TOKEN/');
$result = $sb->getSaleScope()->deliveryHandler()->add([
'CODE' => 'uber',
'NAME' => 'Uber',
'SETTINGS' => [
'CALCULATE_URL' => 'https://gateway.bx/calculate.php',
'CREATE_DELIVERY_REQUEST_URL' => 'https://gateway.bx/create_delivery_request.php',
'CANCEL_DELIVERY_REQUEST_URL' => 'https://gateway.bx/cancel_delivery_request.php',
'HAS_CALLBACK_TRACKING_SUPPORT' => 'Y',
'CONFIG' => [
[
'TYPE' => 'STRING',
'CODE' => 'MY_FIRST_SETTING',
'NAME' => 'My first setting',
],
[
'TYPE' => 'STRING',
'CODE' => 'MY_SECOND_SETTING',
'NAME' => 'My second setting',
],
],
],
'PROFILES' => [
[
'NAME' => 'Taxi',
'CODE' => 'TAXI',
'DESCRIPTION' => 'Taxi Delivery',
],
[
'NAME' => 'Cargo',
'CODE' => 'CARGO',
'DESCRIPTION' => 'Cargo Delivery',
],
],
]);
echo '<PRE>';
print_r($result->getId());
echo '</PRE>';
If the handler is successfully added, the method returns its identifier.
{
"result": 23
}
2. Create a Delivery Service
Create a delivery service using the sale.delivery.add method. Pass the following parameters to the method:
-
REST_CODE— the symbolic code of the delivery service handler. We will specifyuber, which was set in the first step. -
NAME— the name of the delivery service, for example,Uber Taxi. -
CURRENCY— the symbolic code of the currency. We will passEUR. You can retrieve a list of currencies using the crm.currency.list method. -
ACTIVE— the delivery service activity flag. We will specifyY. -
CONFIG— the handler configuration values. We are passing values forMY_FIRST_SETTINGandMY_SECOND_SETTING, which were set in the first step.
const response = await $b24.actions.v2.call.make({
method: 'sale.delivery.add',
params: {
REST_CODE: "uber",
NAME: "Uber Taxi",
CURRENCY: "EUR",
ACTIVE: "Y",
CONFIG: [
{
CODE: "MY_FIRST_SETTING",
VALUE: "My first setting value",
},
{
CODE: "MY_SECOND_SETTING",
VALUE: "My second setting value",
},
]
},
requestId: 'delivery-add'
})
if (response.isSuccess) {
console.info(response.getData().result)
} else {
console.error(response.getErrorMessages().join('; '))
}
try:
response = client.sale.delivery.add(
rest_code="uber",
name="Uber Taxi",
currency="EUR",
active=True,
config=[
{
"CODE": "MY_FIRST_SETTING",
"VALUE": "My first setting value",
},
{
"CODE": "MY_SECOND_SETTING",
"VALUE": "My second setting value",
},
],
).response
print(response.result)
except BitrixAPIError as error:
print(error)
$result = $sb->getSaleScope()->delivery()->add([
'REST_CODE' => 'uber',
'NAME' => 'Uber Taxi',
'CURRENCY' => 'EUR',
'ACTIVE' => 'Y',
'CONFIG' => [
[
'CODE' => 'MY_FIRST_SETTING',
'VALUE' => 'My first setting value',
],
[
'CODE' => 'MY_SECOND_SETTING',
'VALUE' => 'My second setting value',
],
]
]);
echo '<PRE>';
print_r($result->getParent()->ID);
echo '</PRE>';
If the delivery service is successfully created, the method returns a parent service object and an array of profiles. Retain the profile identifiers from the profiles array: they are required to link shipment properties and extra services.
{
"result": {
"parent": {
"NAME": "Uber Taxi",
"ACTIVE": "Y",
"CURRENCY": "EUR",
"ID": 226,
"PARENT_ID": null
},
"profiles": [
{
"NAME": "Taxi",
"ACTIVE": "Y",
"ID": 227,
"PARENT_ID": 226
},
{
"NAME": "Cargo",
"ACTIVE": "Y",
"ID": 228,
"PARENT_ID": 226
}
]
}
}
3. Create Shipment Properties
In a shipment, a manager specifies the origin address and the delivery address. We will sequentially create two properties, Address From and Address To, using the sale.shipmentproperty.add method.
The examples use personTypeId: 3 and propsGroupId: 6. If your Bitrix24 has different payer types or property groups, substitute the values retrieved by the sale.persontype.list and sale.propertygroup.list methods.
Address from Property
Pass the fields object to the method with the values for the Address From property fields.
-
personTypeId— the payer type identifier. We will pass3. You can retrieve the list of types using the sale.persontype.list method. -
propsGroupId— the property group identifier. We will specify6. You can retrieve the list of groups using the sale.propertygroup.list method. -
name— the shipment property name. We will specifyAddress From. -
active— the activity flag. We will passY. -
sort— sorting. -
type— the shipment property type. We will passADDRESS. See the sale.shipmentproperty.add method documentation for a list of possible values. -
required— a flag indicating whether the property is required. We will specifyY. -
isAddressFrom— a flag indicating whether the shipment property is used as the origin address. We will passY.
const response = await $b24.actions.v2.call.make({
method: 'sale.shipmentproperty.add',
params: {
fields: {
personTypeId: 3,
propsGroupId: 6,
name: "Address From",
active: "Y",
sort: "100",
type: "ADDRESS",
required: "Y",
isAddressFrom: "Y"
}
},
requestId: 'shipmentproperty-add-from'
})
if (response.isSuccess) {
console.info(response.getData().result)
} else {
console.error(response.getErrorMessages().join('; '))
}
try:
response = client.sale.shipmentproperty.add(
fields={
"personTypeId": 3,
"propsGroupId": 6,
"name": "Address From",
"active": "Y",
"sort": "100",
"type": "ADDRESS",
"required": "Y",
"isAddressFrom": "Y",
},
).response
print(response.result)
except BitrixAPIError as error:
print(error)
$result = $sb->getSaleScope()->shipmentProperty()->add([
'personTypeId' => 3,
'propsGroupId' => 6,
'name' => 'Address From',
'active' => 'Y',
'sort' => '100',
'type' => 'ADDRESS',
'required' => 'Y',
'isAddressFrom' => 'Y'
]);
echo '<PRE>';
print_r($result->getId());
echo '</PRE>';
If the property is successfully added, the method returns a property object with the property identifier. Retain the property.id value: it is required to link the property to delivery profiles.
{
"result": {
"property": {
"id": 102,
"name": "Address From",
"isAddressFrom": "Y",
"isAddressTo": "N",
"type": "ADDRESS"
}
}
}
Address to Property
In the fields object for the Address To property, we pass the name Address To. The other parameters are similar Address From.
const response = await $b24.actions.v2.call.make({
method: 'sale.shipmentproperty.add',
params: {
fields: {
personTypeId: 3,
propsGroupId: 6,
name: "Address To",
active: "Y",
sort: "100",
type: "ADDRESS",
required: "Y",
isAddressTo: "Y"
}
},
requestId: 'shipmentproperty-add-to'
})
if (response.isSuccess) {
console.info(response.getData().result)
} else {
console.error(response.getErrorMessages().join('; '))
}
try:
response = client.sale.shipmentproperty.add(
fields={
"personTypeId": 3,
"propsGroupId": 6,
"name": "Address To",
"active": "Y",
"sort": "100",
"type": "ADDRESS",
"required": "Y",
"isAddressTo": "Y",
},
).response
print(response.result)
except BitrixAPIError as error:
print(error)
$result = $sb->getSaleScope()->shipmentProperty()->add([
'personTypeId' => 3,
'propsGroupId' => 6,
'name' => 'Address To',
'active' => 'Y',
'sort' => '100',
'type' => 'ADDRESS',
'required' => 'Y',
'isAddressTo' => 'Y'
]);
echo '<PRE>';
print_r($result->getId());
echo '</PRE>';
If the property is successfully added, the method returns a property object with the property identifier. Retain the property.id value: it is required to link the property to delivery profiles.
{
"result": {
"property": {
"id": 103,
"name": "Address To",
"isAddressFrom": "N",
"isAddressTo": "Y",
"type": "ADDRESS"
}
}
}
4. Link Shipment Properties to a Delivery Service
To link properties Address From and Address To to profiles Taxi and Cargo, we will call the sale.propertyRelation.add method four times. We will pass the fields object to the method with field values for linking the properties.
-
entityId— the delivery profile identifier. For profileTaxiwe will pass227, and forCargo—228, which were obtained in the second step. -
entityType— the object type. Possible values:P— payment system,D— delivery,L— landing page,T— trading platform. We will specify the valueD. -
propertyId— the property identifier. ForAddress Fromwe will specify102, and forAddress To—103, which were obtained in the third step.
The values 227, 228, 102, and 103 are examples. In a production scenario, substitute the identifiers from the responses of the sale.delivery.add and sale.shipmentproperty.add methods.
const response = await $b24.actions.v2.call.make({
method: 'sale.propertyRelation.add',
params: {
fields: {
entityId: 227,
entityType: 'D',
propertyId: 102
}
},
requestId: 'propertyrelation-add'
})
if (response.isSuccess) {
console.info(response.getData().result)
} else {
console.error(response.getErrorMessages().join('; '))
}
try:
response = client.sale.propertyrelation.add(
fields={
"entityId": 227,
"entityType": "D",
"propertyId": 102,
},
).response
print(response.result)
except BitrixAPIError as error:
print(error)
$result = $sb->getSaleScope()->propertyRelation()->add([
'entityId' => 227,
'entityType' => 'D',
'propertyId' => 102
]);
Call the sale.propertyRelation.add method sequentially.
-
Service
Taxi, propertyAddress From— passentityId: 227, propertyId: 102. -
Service
Taxi, propertyAddress To— passentityId: 227, propertyId: 103. -
Service
Cargo, propertyAddress From— passentityId: 228, propertyId: 102. -
Service
Cargo, propertyAddress To— passentityId: 228, propertyId: 103.
If the links are successfully added, the method returns objects with information about them.
{
"result": {
"propertyRelation": {
"entityId": 227,
"entityType": "D",
"propertyId": 102
}
}
}
5. Adding Services to Delivery Services
To add an extra service to a delivery service, call the sale.delivery.extra.service.add method. Pass the following parameters to it:
-
DELIVERY_ID— the identifier of the delivery service to which the service will be linked. For theTaxiprofile, we will specify the227identifier obtained in the second step. For other profiles, substitute your own identifier. You can retrieve a list of delivery service identifiers using the sale.delivery.getlist method. -
ACTIVE— the service activity flag. Possible values:Y— yes,N— no. We will passY. -
CODE— the symbolic code of the service. We will specifydoor_delivery. -
NAME— the name of the service, for example,Door Delivery. -
TYPE— the service type. Possible values:enum— list,checkbox— single service,quantity— quantity service. We will specifycheckbox. -
PRICE— the cost of the service type in the delivery service currency. We will specify1000.For services of type
enum, the cost is specified using theITEMSparameter. For more details, see the sale.delivery.extra.service.add method documentation.
const response = await $b24.actions.v2.call.make({
method: 'sale.delivery.extra.service.add',
params: {
DELIVERY_ID: 227,
ACTIVE: "Y",
CODE: "door_delivery",
NAME: "Door Delivery",
TYPE: "checkbox",
PRICE: 1000
},
requestId: 'delivery-extra-service-add'
})
if (response.isSuccess) {
console.info(response.getData().result)
} else {
console.error(response.getErrorMessages().join('; '))
}
try:
response = client.sale.delivery.extra.service.add(
delivery_id=227,
type="checkbox",
name="Door Delivery",
active=True,
code="door_delivery",
price=1000.0,
).response
print(response.result)
except BitrixAPIError as error:
print(error)
$result = $sb->getSaleScope()->deliveryExtraService()->add([
'DELIVERY_ID' => 227,
'ACTIVE' => 'Y',
'CODE' => 'door_delivery',
'NAME' => 'Door Delivery',
'TYPE' => 'checkbox',
'PRICE' => 1000,
]);
echo '<PRE>';
print_r($result->getId());
echo '</PRE>';
If the service is added, the method returns the identifier in the result parameter.
{
"result": 140
}
Delivery Status Notifications
To send notifications regarding the progress of a delivery, you can use the sale.delivery.request.* group of methods.
|
Method |
Description |
|
Updates the delivery order object: status and its set of properties |
|
|
Sends a message to the manager or recipient about the current status of the delivery order |
|
|
Reports the cancellation of a delivery order on the external system side and attempts to cancel the delivery order on the Bitrix24 side |
Check the Result
Open a CRM card with a shipment and verify that the Uber Taxi delivery service, its Taxi and Cargo profiles, the Address From and Address To shipment properties, and the Door Delivery extra service are available.
Through REST, check the created objects using the sale.delivery.getlist, sale.shipmentproperty.list, sale.propertyRelation.list, and sale.delivery.extra.service.get methods.
const deliveryResponse = await $b24.actions.v2.call.make({
method: 'sale.delivery.getlist',
params: {
SELECT: ['ID', 'NAME', 'PARENT_ID', 'ACTIVE'],
FILTER: { '=NAME': 'Uber Taxi' },
},
requestId: 'delivery-getlist-check',
})
const propertyResponse = await $b24.actions.v2.call.make({
method: 'sale.shipmentproperty.list',
params: {
select: ['id', 'name', 'isAddressFrom', 'isAddressTo'],
filter: { '=name': ['Address From', 'Address To'] },
},
requestId: 'shipmentproperty-list-check',
})
const relationResponse = await $b24.actions.v2.call.make({
method: 'sale.propertyRelation.list',
params: {
select: ['entityId', 'entityType', 'propertyId'],
filter: { entityId: 227, entityType: 'D' },
},
requestId: 'propertyrelation-list-check',
})
const extraServiceResponse = await $b24.actions.v2.call.make({
method: 'sale.delivery.extra.service.get',
params: { DELIVERY_ID: 227 },
requestId: 'delivery-extra-service-get-check',
})
console.log(deliveryResponse.getData().result)
console.log(propertyResponse.getData().result)
console.log(relationResponse.getData().result)
console.log(extraServiceResponse.getData().result)
deliveries = token.call_method(
"sale.delivery.getlist",
{
"SELECT": ["ID", "NAME", "PARENT_ID", "ACTIVE"],
"FILTER": {"=NAME": "Uber Taxi"},
},
)
properties = token.call_method(
"sale.shipmentproperty.list",
{
"select": ["id", "name", "isAddressFrom", "isAddressTo"],
"filter": {"=name": ["Address From", "Address To"]},
},
)
relations = token.call_method(
"sale.propertyRelation.list",
{
"select": ["entityId", "entityType", "propertyId"],
"filter": {"entityId": 227, "entityType": "D"},
},
)
extra_services = token.call_method(
"sale.delivery.extra.service.get",
{"DELIVERY_ID": 227},
)
print(deliveries)
print(properties)
print(relations)
print(extra_services)
$deliveryResponse = $sb->core->call('sale.delivery.getlist', [
'SELECT' => ['ID', 'NAME', 'PARENT_ID', 'ACTIVE'],
'FILTER' => ['=NAME' => 'Uber Taxi'],
]);
$propertyResponse = $sb->core->call('sale.shipmentproperty.list', [
'select' => ['id', 'name', 'isAddressFrom', 'isAddressTo'],
'filter' => ['=name' => ['Address From', 'Address To']],
]);
$relationResponse = $sb->core->call('sale.propertyRelation.list', [
'select' => ['entityId', 'entityType', 'propertyId'],
'filter' => ['entityId' => 227, 'entityType' => 'D'],
]);
$extraServiceResponse = $sb->core->call('sale.delivery.extra.service.get', [
'DELIVERY_ID' => 227,
]);
print_r($deliveryResponse->getResponseData()->getResult());
print_r($propertyResponse->getResponseData()->getResult());
print_r($relationResponse->getResponseData()->getResult());
print_r($extraServiceResponse->getResponseData()->getResult());
The scenario is successful if the method responses confirm these results:
sale.delivery.addreturned the parent service in theparentobject and the profiles in theprofilesarraysale.shipmentproperty.addreturned the identifiers of theAddress FromandAddress Topropertiessale.propertyRelation.addreturned the links between the properties and the delivery profilessale.delivery.extra.service.addreturned the identifier of the extra service
Errors and Diagnostics
If a method returns an error, verify the request data and the values passed between the steps.
|
Error code or text |
Cause and action |
|
|
The method was called by a user without administrator permissions |
|
|
A required parameter is missing or a value failed validation. Check |
|
|
A handler with this |
|
|
The delivery service is being created with a |
|
|
An error occurred while adding the service, handler, or extra service. Review |
|
|
No delivery service was found for the specified |
|
|
The property relation request does not include the delivery profile identifier. Take it from the |
|
|
This property relation already exists. You do not need to create it again when rerunning the example |
|
|
The property was not found. Check the |
What to Consider
- rerunning the example with the same
CODEmay fail because the handler code must be unique - delivery profiles are created in step 2; save their identifiers before you configure properties and extra services
- the external service must accept requests on the HTTPS endpoints specified in the handler settings