Методы API
DEVELOPER PLATFORM · v1

API, с которым удобно работать

CRM, кампании, аналитика и интеграции. Точные контракты, понятные примеры и подписанные вебхуки.

Скачать OpenAPI

Private preview: API доступен с проектным ключом. Публичные SLA и сроки поддержки ещё не установлены.

Получайте изменения через вебхуки

Владелец включает «Тариф и кошелёк», «Интеграции», «Задачи» и «Webhook задач» в разделе «Модули». Установите Webhook API по показанным условиям. Для GET /v1/developer/integrations нужен scope integrations.installations.read; для настроек нужны независимые integrations.webhooks.read и integrations.webhooks.write и включённый «API проекта». Метод PUT …/outbound-webhook принимает endpointUrl, eventTypes и expectedVersion.

Для создания expectedVersion = null; для изменения укажите текущую version. Используйте Idempotency-Key. signingSecret показывается только при первом успешном создании или ротации: replay её не возвращает. При потере ответа выполните отдельную ротацию.

Текущий каталог событий

Доступные события зависят от выбранного источника: проверьте eventTypes в ответе состояния webhook. «Webhook задач» поддерживает создание и завершение самостоятельных и клиентских задач, включая задачи API; data.resource различает crm.personal_task и crm.task.

  • crm.deal.created.v1
  • crm.deal.updated.v1
  • crm.deal.stage_changed.v1
  • crm.task.created.v1
  • crm.task.completed.v1
{
  "id": "EVENT_ID",
  "type": "crm.deal.created.v1",
  "api_version": "2026-07-31",
  "created_at": "2026-10-12T12:00:00.000Z",
  "data": {
    "resource": "crm.deal",
    "resource_id": "DEAL_ID"
  }
}

data содержит минимальные идентификаторы, а не карточку клиента. contact_id и stage_id необязательны. Задача может иметь resource = crm.personal_task: отдельный public read для неё пока не обещается. Для текущего состояния сделки или CRM-задачи используйте соответствующий scoped read.

Проверка подписи

v1=HMAC-SHA256(secret, timestamp + "." + rawBody)

  • X-Nova-Webhook-Timestamp — Unix seconds времени попытки, не события.
  • X-Nova-Webhook-Signature — v1= и 64 hex-символа. Сравнивайте constant-time.
  • X-Nova-Webhook-Id — ID доставки. Для dedupe используйте id из JSON.
  • X-Nova-Webhook-Event — тип события, совпадает с type в JSON.

Передайте точные байты до JSON parsing. Пересериализация меняет подпись. Допустимое окно в примере ±300 секунд; часы сервера должны быть синхронизированы. У каждой повторной попытки новая подпись и timestamp.

Скачать проверяющий модуль для Node.js
Исходный код проверяющего модуля
import { createHmac, timingSafeEqual } from "node:crypto";

/** Node 24 receiver helper: supply exact raw bytes and lowercase Node request headers. */
export function verifyNovaWebhook({ body, headers, secret, now = Date.now(), tolerance = 300 }) {
  const reject = () => {
    throw new Error("Invalid Nova webhook");
  };
  if (
    !(body instanceof Uint8Array) ||
    body.byteLength > 256 * 1024 ||
    typeof secret !== "string" ||
    !secret.startsWith("whsec_") ||
    secret.length < 16 ||
    !Number.isFinite(now) ||
    !Number.isFinite(tolerance) ||
    tolerance <= 0 ||
    tolerance > 300
  )
    reject();
  const timestamp = headers?.["x-nova-webhook-timestamp"];
  const signature = headers?.["x-nova-webhook-signature"];
  if (
    typeof timestamp !== "string" ||
    !/^[1-9][0-9]{0,10}$/.test(timestamp) ||
    typeof signature !== "string" ||
    !/^v1=[0-9a-f]{64}$/.test(signature) ||
    Math.abs(Math.floor(now / 1000) - Number(timestamp)) > tolerance
  )
    reject();
  const expected = createHmac("sha256", secret).update(timestamp).update(".").update(body).digest();
  if (!timingSafeEqual(expected, Buffer.from(signature.slice(3), "hex"))) reject();
  let event;
  try {
    event = JSON.parse(Buffer.from(body).toString("utf8"));
  } catch {
    reject();
  }
  if (
    !event ||
    typeof event !== "object" ||
    Array.isArray(event) ||
    typeof event.id !== "string" ||
    !event.id ||
    event.id.length > 160 ||
    typeof event.type !== "string" ||
    !event.type ||
    event.type.length > 160 ||
    event.api_version !== "2026-07-31" ||
    typeof event.created_at !== "string" ||
    !Number.isFinite(Date.parse(event.created_at)) ||
    !event.data ||
    typeof event.data !== "object" ||
    Array.isArray(event.data) ||
    event.type !== headers?.["x-nova-webhook-event"]
  )
    reject();
  // Durable event-id deduplication and enqueue must be implemented by the receiver.
  // Unknown additive event types should be durably acknowledged and skipped safely.
  return event;
}

Надёжный обработчик

  1. Ограничьте body, проверьте подпись и envelope. Invalid signature → 401, invalid JSON → 400.
  2. В одной транзакции сохраните unique event ID и задачу обработки. Дубликат уже сохранённого события → 2xx. Если транзакция не завершилась → 503.
  3. После commit верните 2xx, обработайте событие асинхронно. Не подтверждайте событие до durable enqueue.
  4. Повторы и порядок не гарантируют однократную бизнес-обработку. Делайте handlers идемпотентными, сверяйте текущее состояние через API. Неизвестный additive event можно принять и пропустить.

Доставка и восстановление

Только публичный HTTPS, без redirects. 2xx подтверждает приём; network/timeout/408/425/429/5xx повторяются с ограниченным exponential backoff. Другие ответы становятся dead letter. HTTP transport ограничен 10 секундами. После исправления используйте POST …/outbound-webhook/deliveries/{deliveryId}/retry с новым Idempotency-Key.

Пауза прекращает capture новых событий; автоматический backfill периода паузы не обещается. Ротация заменяет secret немедленно, без overlap. Текущие endpoint и secret используются при попытке доставки, поэтому меняйте endpoint только на подконтрольный получатель. Журнал пока ограничен recent deliveries; полного replay journal и гарантии 72-часового восстановления нет.