Разработчикам

История переписки WhatsApp через API: журнал, авторство и выгрузка в CSV

Обновлено 9 мин чтения

wapi.kz ведёт журнал всех сообщений ваших номеров: входящих, отправленных через API и MCP и набранных менеджером на телефоне. Через API его можно читать постранично, искать по собеседнику и периоду, проверять статус доставки, скачивать фото, голосовые и документы и выгружать переписку файлом или архивом.

Гайд для тех, кто строит отчёты, синхронизирует переписку с CRM или пишет ИИ-агента, которому важно знать, кто и что уже ответил клиенту.

Что хранится в журнале

  • Входящие от клиентов, в том числе из групп, где состоит ваш номер.
  • Исходящие через API, MCP и тестовую кнопку кабинета.
  • Сообщения, которые человек написал с телефона или в WhatsApp Web: номер остаётся у вас, и эти ответы тоже попадают в журнал.
  • Файлы: фото, видео, голосовые, документы и стикеры — входящие и исходящие, в том числе с телефона и в группах.

Журнал ведётся с момента подключения номера и хранится в Казахстане: текст — без срока и без ограничения объёма, файлы — в хранилище, 5 ГБ на аккаунт бесплатно (подробнее — в разделе о хранении). У каждого сообщения есть тип: text, image, video, audio, voice (голосовое), document, sticker, location, contact, media или other.

Журнал: GET /messages

bash
curl -G https://wapi.kz/api/v1/messages \
  -H "Authorization: Bearer $WAPI_KEY" \
  -d from=1 -d chat=77011234567 -d limit=20
ПараметрЧто значит
fromВаш номер: id или телефон. Без него — все номера аккаунта
chatСобеседник: телефон 77011234567 или chat_id (…@c.us, …@g.us, …@lid)
sinceНе раньше этого момента
untilРаньше этого момента (сама граница не входит)
directionin — входящие, out — исходящие
limitСколько вернуть: по умолчанию 50, максимум 500
beforeКурсор следующей страницы — значение next_before

В ответе — массив messages, новые сверху:

JSON
{
  "messages": [
    {
      "id": 1842,
      "from": 1,
      "direction": "out",
      "source": "api",
      "chat_id": "77011234567@c.us",
      "peer": "77011234567",
      "peer_lid": null,
      "name": "Айгерим",
      "is_group": false,
      "group_subject": null,
      "author": null,
      "wa_message_id": "3EB0A1F2C4D5E6F7",
      "type": "text",
      "text": "Ваш заказ №1042 готов к выдаче",
      "status": "read",
      "error": null,
      "created_at": "2026-10-16T09:04:11.482Z",
      "updated_at": "2026-10-16T09:05:02.117Z",
      "media": null,
      "media_skipped": false,
      "media_skip_reason": null,
      "media_pending": false
    }
  ],
  "next_before": null
}

from здесь — id вашего номера, peer — телефон собеседника, name — его имя из WhatsApp или название группы, created_at — время в UTC. Для скрытого номера (…@lid) в peer телефон, если WhatsApp его сообщил, иначе null; сам идентификатор — в peer_lid, и его цифры — не телефон. У групп peer равен null, а у входящих в группе есть author — кто написал: { name, phone, lid }.

Формат времени

since и until принимают дату 2026-10-16, дату со временем 2026-10-16T09:00:00Z или unix-время в секундах. Дата без времени — это полночь по UTC. Чтобы считать сутки по Алматы, укажите смещение: 2026-10-16T00:00:00+05:00. В адресе плюс нужно закодировать как %2B — requests в Python и curl --data-urlencode делают это сами. Неверный формат — ответ 400 с кодом invalid_param.

Пагинация: next_before

Если страница заполнена до limit, в ответе есть next_before — передайте его в параметр before и получите следующую. Следующая страница может оказаться пустой — значит, сообщения кончились. Неполная страница приходит с next_before: null.

Генератор на Python, который проходит все страницы:

Python
import os

import requests

API = "https://wapi.kz/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['WAPI_KEY']}"}


def iter_messages(filters):
    """Все сообщения по фильтру, от новых к старым."""
    params = {"limit": 500, **filters}
    while True:
        r = requests.get(f"{API}/messages", headers=HEADERS, params=params, timeout=30)
        r.raise_for_status()
        data = r.json()
        yield from data["messages"]
        if not data["next_before"]:
            break
        params["before"] = data["next_before"]


for m in iter_messages({"from": 1, "direction": "in", "since": "2026-10-01T00:00:00+05:00"}):
    print(m["created_at"], m["peer"], m["text"])

Одно сообщение и статус доставки

GET /messages/{id} возвращает одно сообщение — например, чтобы проверить, дошло ли отправленное. id приходит в ответе на POST /messages/send.

statusЧто значит
queuedПринято, ждёт отправки
sentОтправлено в WhatsApp (одна галочка)
deliveredДоставлено на телефон (две галочки)
readПрочитано
failedНе доставлено — причина в поле error

В error бывает send_failed, capacity или number_paused — сообщение не ушло, или not delivered — WhatsApp сообщил, что не доставил его. Статус только повышается: прочитанное не станет снова «доставленным». Входящие сохраняются со статусом delivered. Сообщения нет в вашем аккаунте — ответ 404 с кодом not_found.

Опрашивать статус в цикле не нужно: изменения сразу приходят в вебхук событием message.ack.

Файлы: поле media

Если в сообщении было фото, видео, голосовое, документ или стикер, файл лежит в поле media:

JSON
"media": {
  "id": 512, "kind": "photo", "mimetype": "image/jpeg", "filename": null, "size": 184320,
  "url": "https://storage.yandexcloud.kz/wapi-media/…?X-Amz-Signature=…"
}

kind — photo, video, voice, document или sticker. url — подписанная ссылка, она живёт 15 минут. Нужна свежая — GET /media/{id} ответит переадресацией 302 на новую ссылку, а с ?download=1 отдаст файл с исходным именем:

bash
curl -L "https://wapi.kz/api/v1/media/512?download=1" -H "Authorization: Bearer $WAPI_KEY" -o check.jpg

Файл был, но не сохранён — media_skipped: true, причина в media_skip_reason: history_off — хранение переписки выключено, ceiling — достигнут ваш потолок объёма, no_balance — сверх 5 ГБ не хватает баланса, failed — WhatsApp не отдал файл. media_pending: true значит, что файл ещё сохраняется — обычно это секунды, запросите сообщение чуть позже. Ключ одного номера видит только файлы своего номера. Подробнее — в документации по файлам.

Чаты: GET /chats

GET /chats?from=&limit= — собеседники с последним сообщением и счётчиками incoming, outgoing, total, свежие сверху. Удобно для своей панели «входящих» или чтобы найти клиентов, которые написали и не дождались ответа. Пример на Node.js (18+, ES-модуль):

JavaScript
const API = "https://wapi.kz/api/v1";
const headers = { Authorization: `Bearer ${process.env.WAPI_KEY}` };

const { chats } = await fetch(`${API}/chats?from=1&limit=200`, { headers }).then((r) => r.json());
const waiting = chats.filter((c) => c.peer && c.last.direction === "in");
for (const c of waiting) console.log(`${c.peer} ждёт ответа с ${c.last_at}: ${c.last.text ?? "[без текста]"}`);

Поле source: кто написал

Номер остаётся на телефоне, поэтому в одном чате могут писать программа, ИИ-агент и живой менеджер. Поле source различает их:

sourceКто написал
apiВаша программа через REST API
mcpИИ-агент через MCP-сервер
testТестовая отправка из кабинета
phoneЧеловек с телефона или в WhatsApp Web, Desktop
contactСобеседник
systemСлужебное сообщение сервиса

Главное применение — чтобы ИИ-агент не перебивал менеджера. Перед ответом агент смотрит последние сообщения чата: если недавно там есть source: "phone", разговор ведёт человек, и агенту лучше промолчать. Продолжение примера выше:

JavaScript
async function agentMayReply(from, client) {
  const url = `${API}/messages?from=${from}&chat=${client}&limit=10`;
  const { messages } = await fetch(url, { headers }).then((r) => r.json());
  const hourAgo = Date.now() - 60 * 60 * 1000;
  return !messages.some((m) => m.source === "phone" && Date.parse(m.created_at) > hourAgo);
}

Агент на MCP получает те же поля через инструмент get_messages — подробнее в гайде про ИИ-агентов и MCP и в документации.

Выгрузка в CSV и JSON

GET /export отдаёт переписку файлом: для отчётов, архива или переноса в свою базу. До 100 000 сообщений за раз, старые сверху. from, since, until — как в GET /messages, format — csv (по умолчанию) или json.

bash
curl -G https://wapi.kz/api/v1/export \
  -H "Authorization: Bearer $WAPI_KEY" \
  -d from=1 -d since=2026-10-01 -d until=2026-11-01 -d format=csv \
  -o whatsapp-october.csv

Колонки CSV: id, created_at, number_id, number_phone, direction, source, peer_phone, chat_id, type, text, status, wa_message_id, error. Файл в UTF-8 с меткой BOM, поэтому кириллица в Excel открывается нормально. Если Excel сложил всё в одну колонку, откройте файл через «Данные → Из текста/CSV» и выберите разделитель «запятая».

Если текст начинается с =, +, - или @, в CSV перед ним ставится апостроф — чтобы Excel не принял сообщение клиента за формулу. Обрабатывая CSV программой, учитывайте это или берите JSON: там текст как есть.

JSON-выгрузка — сообщения в том же виде, что в GET /messages, плюс from_phone (телефон вашего номера) и общее число count. Месячный отчёт «сколько сообщений и от кого» на Python — API и HEADERS из генератора выше:

Python
from collections import Counter

r = requests.get(f"{API}/export", headers=HEADERS, timeout=120, params={
    "since": "2026-10-01T00:00:00+05:00",
    "until": "2026-11-01T00:00:00+05:00",
    "format": "json",
})
r.raise_for_status()
stats = Counter((m["from_phone"] or str(m["from"]), m["source"]) for m in r.json()["messages"])
for (number, source), count in sorted(stats.items()):
    print(number, source, count)

Больше 100 000 сообщений — выгружайте частями, по неделям или месяцам. Подробности — в документации по выгрузке.

Архив вместе с файлами

GET /export выгружает только сообщения. Переписку вместе с файлами собирает POST /exports — ZIP-архив с папкой файлов, messages.json и messages.csv для Excel:

bash
curl -X POST https://wapi.kz/api/v1/exports \
  -H "Authorization: Bearer $WAPI_KEY" -H "Content-Type: application/json" \
  -d '{"from":1,"since":"2026-10-01","until":"2026-11-01","include_media":true}'

Все поля необязательные: без from — все номера, без chat — все чаты, без дат — с момента подключения. Архив собирается в фоне, обычно за минуты. Статус и ссылку показывает GET /exports/{id}: queued → running → done (или failed, expired), а когда архив готов, на вебхук приходит событие export.ready. Ссылка живёт 7 дней. Одновременно — не больше 2 выгрузок, в сутки — до 10. Подробнее — в документации по архивам.

Догон пропущенных вебхуков

Вебхуки не отправляются повторно: если ваш сервер был недоступен, события за это время потеряны для вебхука, но не для журнала. Схема догона простая: запоминайте wapi.message_id обработанных событий — это id в журнале — и после простоя запрашивайте входящие с запасом по времени:

Python
def catch_up(since_iso, seen_ids):
    """Входящие с момента since_iso, которые не пришли вебхуком, от старых к новым."""
    missed = [m for m in iter_messages({"since": since_iso, "direction": "in"}) if m["id"] not in seen_ids]
    return list(reversed(missed))

Хранение: без срока, файлы и тексты

Тексты хранятся без срока и без ограничения объёма — автоматически ничего не удаляется. Файлы лежат в хранилище в Казахстане (Yandex Cloud), зашифрованы, есть резервная копия. 5 ГБ на аккаунт — сумма по всем номерам — бесплатно, сверх — 1 000 ₸ в месяц за каждые 5 ГБ; пакеты крупнее дешевле: 50 ГБ — 7 900 ₸, 200 ГБ — 19 900 ₸, самый выгодный набор выбираем сами. Списываем с баланса по дням, по фактическому объёму. Сколько занято, видно в кабинете и в GET /storage — см. документацию.

В кабинете, в разделе «Хранилище», можно поставить потолок «не хранить больше N ГБ»: сверх него новые файлы просто не сохраняются, доплат нет. Там же включается автоудаление файлов старше 30, 90, 180 или 365 дней — по умолчанию оно выключено. Через API файлы удаляются по одному — DELETE /media/{id} — или пачкой — POST /media/delete. Удалённый файл сразу пропадает из хранилища, резервная копия — через 30 дней. Если удалить номер, его чаты и файлы хранятся ещё 30 дней, потом удаляются.

Если место сверх 5 ГБ не оплачено, новые файлы сверх 5 ГБ не сохраняются, а читать и выгружать сохранённое можно. Мы предупреждаем в кабинете, в WhatsApp и событием storage.warning на вебхук, а через 30 дней неуплаты удаляем самые старые файлы сверх 5 ГБ.

Хранение переписки можно выключить в настройках аккаунта. Тогда у сообщений остаются время, направление, собеседник и статус, а text равен null, и новые файлы не сохраняются; уже сохранённые тексты при выключении стираются. Учтите: без текстов догон пропущенных вебхуков и выгрузка покажут только факт сообщения. Как это связано с требованиями к персональным данным — в гайде про локализацию данных в РК (это не юридическая консультация).

История, чаты и выгрузки входят в стоимость номера — 9 900 ₸ в месяц, файлы — 5 ГБ на аккаунт бесплатно, см. цены. Начать можно с регистрации.

Частые вопросы

Можно ли получить переписку, которая была до подключения к wapi.kz?

Нет. Журнал ведётся с момента подключения номера; старые чаты остаются в WhatsApp на телефоне.

Сколько хранится история сообщений?

Без срока. Тексты хранятся без ограничений, файлы — тоже без срока, пока помещаются в бесплатные 5 ГБ на аккаунт или оплачено место сверх них. Старые файлы можно удалять автоматически — это включается в кабинете.

Попадают ли в историю сообщения, которые менеджер пишет с телефона?

Да, с пометкой source: "phone". Это же событие приходит в вебхук как message.out.

Как выгрузить больше 100 000 сообщений?

Частями: разбейте период на недели или месяцы и сделайте несколько запросов GET /export с since и until.

Почему в CSV перед некоторыми сообщениями стоит апостроф?

Это защита от формул: текст, который начинается с =, +, - или @, Excel мог бы выполнить как формулу. В JSON-выгрузке текст приходит без изменений.

Подключите WhatsApp к своим системам

9 900 ₸ в месяц за номер, без платы за сообщения. Сервер в Казахстане, оплата Kaspi или счётом на ТОО/ИП. Подключение — около 5 минут.