Облачные функции CronBox: 5 примеров автоматизации на Python и Node.js
Преобразование заказов, контроль остатков, заявки в CRM, сводные отчёты и результаты CI. Практическое руководство с кодом и запуском через API.
Магазин присылает заказ в одном формате, а система учёта ждёт другой. Форма собирает заявку, но перед отправкой в CRM нужно проверить поля. Для отчёта требуется получить данные из двух API и посчитать несколько показателей. Во всех этих случаях полезен небольшой обработчик, который можно разместить в облачной функции CronBox.
В этой статье разберём пять сценариев и три примера кода. Начнём с преобразования JSON без внешних запросов, затем подключим API и секреты.
Как устроена облачная функция
Функция — один файл на Python или Node.js с обработчиком handler(event, context). В event приходит JSON вашего вызова, а context даёт доступ к секретам, идентификатору исполнения и HTTP-клиенту. Обработчик возвращает JSON-совместимый результат.
Вызов асинхронный: API принимает задачу в очередь и возвращает execution_id. Когда исполнение завершится, CronBox отправит итог на указанный вами HTTPS webhook. Состояние исполнения и доставки, результат, ошибки и логи доступны в истории панели.
Для создания и запуска нужна активная подписка на тариф с поддержкой функций. Количество функций и вызовов определяется текущим тарифом; свои квоты смотрите в панели, актуальные условия — в тарифах.
Функции подходят для коротких обработчиков: выполнение ограничено 60 секундами, установка сторонних пакетов и постоянное хранилище не поддерживаются. Сетевые обращения выполняются через context.http к публичным HTTPS API. Прямые fetch, urllib и сокеты для доступа в сеть не подходят.
Перед первым запуском
- Откройте «Облачные функции» в панели и создайте функцию.
- Выберите Python или Node.js и вставьте код одного из примеров ниже.
- Укажите свой HTTPS webhook для результата. Он обязателен даже для функции без внешних запросов.
- Настройте сетевой режим. Для примера без запросов выберите
allowlistс пустым списком; для интеграции добавьте точные домены используемых API. - Сохраните черновик. Если пример использует токен, добавьте его в разделе «Секреты».
- Опубликуйте функцию, отправьте тестовый JSON и откройте историю исполнения.
Все адреса на example.com в примерах — учебные заглушки. Замените их адресами своих сервисов и согласуйте структуру запросов и ответов с их API. Готовые обработчики не требуют установки пакетов.
1. Привести заказ к формату системы учёта
Ситуация: интернет-магазин передаёт позиции заказа с количеством и ценой. Система учёта ждёт итоговую сумму и единое название поля с номером заказа.
Функция проверит вход, посчитает сумму и вернёт нормализованный объект. Получатель итогового webhook сможет записать его в систему учёта. Внешние запросы из самой функции здесь не нужны.
Среда: Python. Сеть: allowlist, пустой список доменов.
Цены передаём целым числом копеек: это позволяет обойти ошибки вычислений с дробными денежными суммами. Пример рассчитан на заказы в рублях, где цена включает все необходимые начисления; скидки и доставку при необходимости нужно учесть отдельно.
def handler(event, context):
order_id = event.get("order_id")
items = event.get("items")
if not isinstance(order_id, str) or not order_id.strip():
raise ValueError("order_id is required")
if not isinstance(items, list) or not items:
raise ValueError("items must be a non-empty list")
total = 0
units = 0
for item in items:
quantity = item["quantity"]
price = item["price_kopecks"]
if type(quantity) is not int or quantity <= 0:
raise ValueError("quantity must be a positive integer")
if type(price) is not int or price < 0:
raise ValueError("price_kopecks must be a non-negative integer")
total += quantity * price
units += quantity
return {
"external_id": order_id.strip(),
"currency": "RUB",
"total_kopecks": total,
"units": units,
}Вход для проверки:
{
"order_id": "shop-1042",
"items": [
{"quantity": 2, "price_kopecks": 149900},
{"quantity": 1, "price_kopecks": 50000}
]
}Результат обработчика:
{
"external_id": "shop-1042",
"currency": "RUB",
"total_kopecks": 349800,
"units": 3
}Это 3 498 рублей за три единицы товара. Такой же подход подходит для переименования полей, фильтрации событий и подготовки данных для импорта. Результат попадёт в поле result итогового webhook, вместе со статусом и идентификатором исполнения.
2. Выделить товары с низким остатком
Ситуация: отдел закупок хочет получать список позиций, которых осталось меньше заданного порога. Выгрузка склада большая для ручной проверки, но помещается в ограничения короткого API-запроса.
Функция запросит остатки, отфильтрует товары и вернёт список для закупки. Получатель результата может добавить его в рабочий кабинет или передать в сервис почтовых уведомлений.
Среда: Node.js. Сеть: allowlist с доменом вашего API. Секрет: WAREHOUSE_API_TOKEN.
Для примера предполагаем, что GET /stock возвращает объект с массивом items, где у каждого товара есть строковый sku и целочисленный available. Ответ содержит полную выборку; если ваш API использует пагинацию, её нужно реализовать отдельно с учётом лимитов функции.
export async function handler(event, context) {
const threshold = event.threshold ?? 5;
if (!Number.isInteger(threshold) || threshold < 1) {
throw new Error("threshold must be a positive integer");
}
const response = await context.http.request({
method: "GET",
url: "https://warehouse.example.com/stock",
headers: {
Authorization: "Bearer " + context.secrets.WAREHOUSE_API_TOKEN
}
});
if (response.status !== 200) {
throw new Error("Warehouse API returned " + response.status);
}
const data = JSON.parse(response.body);
if (!Array.isArray(data.items)) {
throw new Error("Warehouse API must return items");
}
const lowStock = [];
for (const item of data.items) {
if (typeof item.sku !== "string" ||
!Number.isInteger(item.available) || item.available < 0) {
throw new Error("Invalid stock item");
}
if (item.available < threshold) {
lowStock.push({sku: item.sku, available: item.available});
}
}
return {threshold, count: lowStock.length, items: lowStock};
}Вход для проверки:
{"threshold": 5}Обратите внимание: response.body — строка, поэтому для JSON-ответа нужен JSON.parse. HTTP-ошибка 4xx или 5xx сама по себе не вызывает исключение в обработчике: статус нужно проверить, как в примере. Иначе функция может завершиться успешно с ошибочным ответом внешнего сервиса.
Если проверка нужна каждое утро: у функций пока нет собственного расписания и встроенного шага в цепочках CronBox. Настройте вызов API функции из внешнего планировщика или своего приложения. Каждый новый запуск должен получать новый Idempotency-Key, а повтор того же запуска после потери ответа — прежний ключ и прежний JSON.
3. Проверить заявку и передать её в CRM
Ситуация: ваше приложение принимает заявку с формы. Перед созданием обращения в CRM нужно удалить лишние пробелы и проверить обязательные поля.
Пусть ваш backend проверяет запрос формы и вызывает функцию. Функция подготавливает данные и обращается к CRM с токеном из секретов. API-ключ CronBox хранится на backend: его нельзя размещать в браузерном коде формы.
Среда: Python. Сеть: allowlist с доменом CRM. Секрет: CRM_API_TOKEN.
Учебный API принимает POST /leads, возвращает 201 и JSON с полем id. В этом примере он также поддерживает заголовок Idempotency-Key: повтор с тем же ключом и телом возвращает ранее созданную заявку. Проверьте этот контракт у своей CRM до использования.
import json
async def handler(event, context):
name = event.get("name")
email = event.get("email")
if not isinstance(name, str) or not name.strip():
raise ValueError("name is required")
if not isinstance(email, str) or not email.strip():
raise ValueError("email is required")
response = await context.http.request(
"POST",
"https://crm.example.com/leads",
headers={
"Authorization": "Bearer " + context.secrets["CRM_API_TOKEN"],
"Idempotency-Key": context.execution_id,
},
json={"name": name.strip(), "email": email.strip()},
)
if response["status"] != 201:
raise RuntimeError("CRM API returned " + str(response["status"]))
lead = json.loads(response["body"])
return {"lead_id": lead["id"], "created": True}Вход для проверки:
{"name": " Анна ", "email": "anna@example.com"}Здесь проверяется наличие полей. Проверку адреса, согласия на обработку данных и защиту формы от спама реализуйте в принимающем приложении по своим правилам.
Идемпотентность вызова CronBox защищает от повторного запуска при повторе того же запроса. Она не делает запись во внешнюю CRM автоматически безопасной: для этого нужна поддержка на стороне CRM. Новый намеренный вызов функции получит другой execution_id; если один и тот же лид может приходить несколько раз, передавайте стабильный идентификатор заявки и используйте его для дедупликации в CRM.
4. Собрать сводный отчёт из нескольких API
Ситуация: продажи находятся в магазине, а расходы на продвижение — в отдельном сервисе. Руководителю нужна краткая сводка за выбранную дату.
Передайте функции дату отчёта. Обработчик может получить агрегированные продажи и расходы через context.http, вычислить разницу и вернуть объект с показателями. Получатель webhook сохранит отчёт или отправит его через ваш почтовый сервис.
Пример входа:
{"date": "2026-10-15"}Договоритесь об одинаковых валюте, часовом поясе и правилах учёта возвратов в обоих источниках. Разница между выручкой и рекламными расходами сама по себе не является прибылью: для прибыли нужны себестоимость, комиссии и другие расходы.
Этот сценарий подходит, если источники уже умеют выдавать небольшие агрегаты. Выгрузку миллионов записей лучше выполнять отдельным процессом: у функции нет постоянного диска, а время, трафик и размер результата ограничены.
5. Превратить результат CI в событие для рабочего кабинета
Ситуация: после сборки или тестов хочется обновлять статус релиза в своём кабинете, сохраняя единый формат событий от разных проектов.
Добавьте в доверенный шаг CI вызов функции с названием проекта, версией, статусом и ссылкой на сборку. Функция проверит допустимые статусы, приведёт поля к общему формату и вернёт событие получателю webhook.
Пример входа:
{
"project": "storefront",
"version": "1.8.0",
"status": "passed",
"build_url": "https://ci.example.com/builds/1042"
}Для повторных попыток отправки одного события используйте стабильный ключ, например storefront-build-1042-completed. Если содержимое события изменилось, это уже отдельный вызов с новым ключом. API-ключ с правом functions:invoke сохраните в секретах CI и не передавайте его в недоверенные сборки из внешних pull request.
Адрес вызова функции требует авторизации и обязательного ключа идемпотентности. Сервис, умеющий отправлять только анонимный webhook, не сможет обращаться к нему напрямую: нужен ваш принимающий endpoint, который проверит подпись исходного события и затем вызовет CronBox.
Как вызвать функцию через API
Один и тот же способ вызова подходит для всех сценариев. Ниже показан запрос для первого примера. Подставьте идентификаторы рабочего пространства и опубликованной функции. Значение YOUR_SCOPED_API_KEY замените ключом с правом functions:invoke; в рабочем приложении загружайте ключ из защищённых настроек.
curl --request POST \
'https://api.cronbox.ru/v1/workspaces/WORKSPACE/functions/FUNCTION/invoke' \
--header 'Authorization: Bearer YOUR_SCOPED_API_KEY' \
--header 'Idempotency-Key: shop-1042-normalize-v1' \
--header 'Content-Type: application/json' \
--data '{"order_id":"shop-1042","items":[{"quantity":2,"price_kopecks":149900},{"quantity":1,"price_kopecks":50000}]}'Успешное принятие возвращает HTTP 202:
{"execution_id":"EXECUTION_ID","status":"queued"}202 подтверждает постановку в очередь. Проверяйте завершение в истории или по итоговому webhook. Если ответ на вызов потерялся, повторите тот же JSON с тем же ключом: CronBox вернёт исходный идентификатор исполнения без повторного запуска. Другой JSON с уже занятым ключом приведёт к 409. Идемпотентность хранится семь дней; для долгосрочной защиты от дублей ведите собственный учёт бизнес-событий.
Как принять результат и проверить ошибки
В итоговом POST приходят event со значением function.completed, стабильный event_id, идентификаторы функции, версии и исполнения, а также status, result, error и duration_ms. Полезные данные из return обработчика находятся в result. Получатель должен сначала проверить подпись, затем статус исполнения и только после этого использовать результат.
CronBox подписывает webhook через HMAC-SHA256. Ключ подписи показывается однократно: сохраните его у получателя. Проверяйте подпись из X-Webhook-Signature по исходным байтам тела и допустимому времени сообщения. Формулу и пример проверки смотрите в документации webhook.
Доставка может повторяться. После проверки подписи получатель должен атомарно учесть event_id и исключить повторный побочный эффект. Успешно принятому событию и его дубликату отвечайте 2xx. Повтор доставки результата не выполняет код функции заново.
При отладке проверяйте отдельно исполнение и доставку: обработчик может успешно завершиться, а ваш webhook — оказаться недоступным. Не выводите секреты и полные персональные данные в логи; для связи записей используйте execution_id. История хранится семь дней, поэтому нужные бизнес-результаты сохраняйте в своей системе.
Что проверить перед рабочим запуском
- Доступ и квоты. Тариф должен поддерживать функции; оцените количество вызовов с учётом тестовых запусков.
- Сеть. Добавьте точные домены внешних API в
allowlist. Поддомены не разрешаются автоматически, перенаправления не выполняются. - Секреты. Храните токены в разделе «Секреты», а ключ подписи — у получателя результата.
- Версия. После изменения кода, сети или адреса webhook сохраните черновик и опубликуйте новую версию. Одно сохранение черновика не меняет исполняемый код.
- Ошибки. Проверьте неверный вход, отказ внешнего API и недоступность получателя результата.
- Ограничения. Один запуск — до 60 секунд и 20 HTTP-запросов; один HTTP-запрос — до 10 секунд. Вход и результат — до 1 MiB каждый. Остальные пределы доступны в документации и API capabilities.
Начать удобно с первого примера: он позволяет проверить публикацию, вызов, историю и доставку результата без подключения стороннего API. Затем можно добавить секреты и сетевые запросы под свою задачу.
Открыть облачные функции · Прочитать документацию · Посмотреть тарифы