Webhooks

Amobit Chats отправляет события в ваш HTTPS-эндпоинт асинхронно, с повторами и подписью HMAC-SHA256. Событие содержит диалог, контакт, сообщение и профиль канала — этого достаточно, чтобы создать обращение в CRM без дополнительных запросов.

Событие: message.createdevent_version: 2026-07-03Подпись: hmac-sha256-v1

Событие message.created

Событие создаётся для входящего сообщения от клиента и для исходящего из кабинета, API или коннектора. Событие для исходящего сообщения формируется после того, как отправка подтверждена локально, поэтому ваше собственное сообщение тоже может прийти в webhook. Внутренние заметки операторов во внешнюю систему не отправляются.

Фильтруйте по message.direction. incoming — от клиента, outgoing — ответ из Amobit. Служебные сообщения с direction: internal в вебхуки не попадают.

Поле верхнего уровняСмысл
eventВсегда message.created.
event_versionВерсия схемы события. Текущая — 2026-07-03. Сравнивайте с ожидаемой и не падайте на неизвестной версии.
workspaceКомпания: id, slug, name.
threadДиалог: id, канал, external_chat_id, имя/телефон, группа, статус, непрочитанные, updated_at.
contactКонтакт Amobit: id, отображаемое имя, заметки.
messageСообщение: направление, тип, текст, автор, статус доставки, идентификаторы провайдера, время.
channel_profileПодключённый канал: id, имя, тип, статус.
attachmentsМассив вложений: тип, имя, MIME, размер, ссылки, размеры, длительность.

Доставка и повторы

Заголовки запроса

ЗаголовокПримерНазначение
X-Amobit-Chats-Eventmessage.createdТип события.
X-Amobit-Chats-Event-Idf09de45d-…ID сообщения (стабильный идентификатор события).
X-Amobit-Chats-Delivery-Id6a1c…ID доставки события. Не меняется между попытками; счётчика попыток в заголовках нет.
X-Amobit-Chats-Workspaceclient-workspaceSlug компании.
X-Amobit-Chats-Timestamp1786010487Unix-время подписи. Меняется при каждой попытке.
X-Amobit-Chats-Signature-Versionhmac-sha256-v1Алгоритм подписи.
X-Amobit-Chats-Signaturesha256=…HMAC-SHA256 от {timestamp}.{raw_body}.
X-Amobit-Chats-Idempotency-Keyexternal:webhook:…Ключ дедупликации на стороне Amobit.

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

Строка подписи — {timestamp}.{raw_body}: значение заголовка X-Amobit-Chats-Timestamp, точка, затем сырое тело запроса в байтах. Ключ — секрет, заданный в настройках интеграции. Сравнивайте хеши за постоянное время.

Не пересобирайте JSON. Подпись считается по исходным байтам. Если распарсить тело и сериализовать заново, порядок ключей и пробелы изменятся, и проверка ложно провалится. Храните raw body до проверки подписи.

Node.js

import crypto from "node:crypto";

export function verifyAmobitSignature({ rawBody, timestamp, signature, secret }) {
  const expected =
    "sha256=" +
    crypto.createHmac("sha256", secret)
      .update(`${timestamp}.${rawBody}`)
      .digest("hex");

  const a = Buffer.from(signature);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Express: нужен raw body, не JSON-парсер
// app.post("/amobit/webhook", express.raw({ type: "application/json" }), handler)

PHP

$raw = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_AMOBIT_CHATS_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_AMOBIT_CHATS_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $raw, getenv('AMOBIT_WEBHOOK_SECRET'));

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit;
}

Python (Django / FastAPI)

import hmac, hashlib

def verify(raw_body: bytes, timestamp: str, signature: str, secret: str) -> bool:
    signed = timestamp.encode() + b"." + raw_body
    expected = "sha256=" + hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

# Django: request.body — сырые байты; не вызывайте request.json() до проверки.
# FastAPI: await request.body() до разбора.

Дополнительно рекомендуется:

Payload

{
  "event": "message.created",
  "event_version": "2026-07-03",
  "workspace": {
    "id": "0d2b5e7c-0000-4000-9000-000000000001",
    "slug": "client-workspace",
    "name": "Client Workspace"
  },
  "thread": {
    "id": "9fd48a99-f5f3-4c3d-8ca1-1f0e047d5b25",
    "channel": "whatsapp",
    "external_chat_id": "79990001122@c.us",
    "display_name": "Client",
    "phone": "79990001122",
    "contact_name": "Client",
    "is_group": false,
    "status": "open",
    "unread_count": 1,
    "updated_at": "2026-10-20T12:00:00+00:00"
  },
  "contact": {
    "id": "3c16c8d6-0000-4000-9000-000000000002",
    "display_name": "Client",
    "notes": ""
  },
  "message": {
    "id": "f09de45d-0000-4000-9000-000000000003",
    "direction": "incoming",
    "message_type": "text",
    "text": "Здравствуйте",
    "preview_text": "Здравствуйте",
    "sender_name": "Client",
    "delivery_status": "delivered",
    "provider_message_id": "provider-message-1",
    "provider_transport_id": "",
    "source": "",
    "sent_at": "2026-10-20T12:00:00+00:00",
    "created_at": "2026-10-20T12:00:01+00:00",
    "updated_at": "2026-10-20T12:00:01+00:00"
  },
  "channel_profile": {
    "id": "6c7b41f5-0000-4000-9000-000000000004",
    "name": "WhatsApp Sales",
    "channel": "whatsapp",
    "status": "connected",
    "support_code": ""
  },
  "attachments": []
}
ПолеТипПримечание
message.directionenumincoming — от клиента, outgoing — из Amobit, internal — служебное.
message.message_typeenumtext, image, video, audio, ptt, document, file, location, reaction, internal_note.
message.delivery_statusenumpending, sent, delivered, read, failed.
message.sourcestringИсточник, если известен. Для сообщений, отправленных через этот API, — external_api: удобно, чтобы не зациклить обработку собственных ответов.
message.textstringТекст или подпись к медиа; может быть пустым для служебных типов.
attachments[]arrayid, kind, file_name, mime_type, size_bytes, share_url, source_url, storage_status, duration_ms, width, height. Ссылки могут быть пустыми, если файл ещё не сохранён.

Как отвечать

Ограничения