Все статьи
16 октября 2026 г.11 мин

Облачные функции CronBox: 5 примеров автоматизации на Python и Node.js

Преобразование заказов, контроль остатков, заявки в CRM, сводные отчёты и результаты CI. Практическое руководство с кодом и запуском через API.

облачные функцииserverless функцииавтоматизация на PythonNode.jsинтеграция 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. Затем можно добавить секреты и сетевые запросы под свою задачу.

Открыть облачные функции · Прочитать документацию · Посмотреть тарифы

Готовы автоматизировать задачи?

Попробуйте CronBox бесплатно - настройка за 2 минуты

Начать бесплатно