Сценарии

Уведомления о заказах Kaspi в WhatsApp: как настроить через API

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

Покупатель оформил заказ на Kaspi и ждёт: приняли ли, где забирать, когда приедет курьер. Короткое сообщение от магазина в WhatsApp снимает эти вопросы и звонки «а где мой заказ». Ниже — как связать Kaspi Магазин API и wapi.kz: скрипт сам замечает новое состояние заказа и пишет покупателю с вашего номера.

Понадобятся токен Kaspi Магазин API, номер WhatsApp, подключённый к wapi.kz, и сервер, где скрипт запускается по расписанию.

Как это устроено

У Kaspi нет вебхуков для продавцов: магазин не узнает о новом заказе, пока сам не спросит. Поэтому схема — опрос по таймеру:

  1. Раз в 5–10 минут скрипт запрашивает заказы у Kaspi Магазин API (GET /orders) с фильтром по состоянию и дате создания.
  2. Для каждого заказа проверяет свою базу: уведомляли ли уже покупателя об этом состоянии.
  3. Если нет — отправляет сообщение через POST /messages/send и записывает отметку «уведомлён».
  4. Ответы покупателей приходят на телефон и вебхуком — их видит менеджер.

Пункт 2 — главный. Каждый опрос возвращает одни и те же заказы снова и снова, и без отметки покупатель получит десяток одинаковых сообщений. Храните пару «id заказа + состояние»: сообщение уходит один раз на каждый переход.

Запросы к Kaspi идут с заголовком X-Auth-Token — токен выдаётся в кабинете продавца Kaspi. Kaspi режет «небраузерные» запросы, поэтому нужен обычный браузерный User-Agent, иначе соединение просто оборвётся.

Что писать покупателю

Статус заказа покупатель и так видит в приложении Kaspi, поэтому дублировать его смысла мало. Пишите то, чего Kaspi не скажет: адрес и часы самовывоза, как найти вход, когда позвонит курьер, кому задать вопрос.

Состояние в KaspiКогдаЧто сказать
NEWПришёл новый заказПолучили, проверяем наличие
PICKUPЗаказ на самовывозАдрес, часы работы, что назвать на выдаче
DELIVERYДоставляете выПередан курьеру, он позвонит перед приездом
KASPI_DELIVERYДоставляет KaspiОбычно писать не нужно

Точный смысл каждого состояния и момент перехода сверьте с документацией Kaspi и проверьте на нескольких своих заказах. Если «собран и готов к выдаче» у вас отдельный шаг, этот момент удобнее брать из своей учётной системы — например, из 1С.

Здравствуйте! Это магазин «{магазин}» на Kaspi. Заказ №{номер} принят.
Самовывоз: Алматы, ул. Абая, 10, ежедневно с 10:00 до 20:00.
Вопросы по заказу — ответьте на это сообщение.

Представьтесь в первой строке: покупатель не знает ваш номер. Одно сообщение — один повод, номер заказа обязателен. Ночью не пишите — заказ подождёт до утра.

Правила: только по заказу

Пишите покупателю только по его заказу. Без рекламы, акций, «подпишитесь на наши соцсети» и предложений «в следующий раз купите напрямую, будет дешевле». Увод покупателя с площадки маркетплейсы, как правило, запрещают. Перед запуском перечитайте актуальные правила Kaspi для продавцов.

То же правило бережёт ваш номер: сообщение, которого человек ждёт, — нормальная переписка, а холодная реклама незнакомым — главный повод для жалоб и блокировок. Подробнее — в гайде как снизить риск блокировки.

Настройка по шагам

Шаг 1. Получите токен Kaspi Магазин API

Токен выпускается в кабинете продавца Kaspi. Храните его в переменной окружения, а не в коде: он даёт доступ к заказам магазина.

Шаг 2. Подключите номер к wapi.kz

Зарегистрируйтесь, пополните баланс, добавьте номер и привяжите его по QR-коду или коду по номеру — это около 5 минут. В разделе «API и MCP» возьмите API-ключ. Подрядчику лучше выдать ключ номера: он работает только с одним номером.

Шаг 3. Первый запуск — с флагом --init

Скрипт запишет текущие заказы как уже уведомлённые и ничего не отправит. Без этого покупатели прошлой недели получат сообщения задним числом.

Шаг 4. Поставьте опрос по расписанию

bash
# раз в 10 минут; KASPI_TOKEN, WAPI_KEY, KASPI_PHONE_FIELD — в файле .env
*/10 * * * * cd /opt/kaspi-notify && set -a && . ./.env && python3 kaspi_notify.py >> notify.log 2>&1

Шаг 5. Задайте вебхук для ответов

Адрес задаётся в кабинете на карточке номера — подробности ниже.

Пример скрипта на Python

Скрипт опрашивает Kaspi, проверяет, есть ли у покупателя WhatsApp (/contacts/check), отправляет сообщение и ведёт отметки в SQLite. Поля заказа сверьте с документацией Kaspi: здесь используются только id, номер заказа и телефон покупателя.

Python
# kaspi_notify.py — pip install requests; первый запуск: python3 kaspi_notify.py --init
import os, sqlite3, sys, time
from datetime import datetime
from zoneinfo import ZoneInfo
import requests

KASPI = "https://kaspi.kz/shop/api/v2"
WAPI = "https://wapi.kz/api/v1"
KASPI_HEADERS = {
    "X-Auth-Token": os.environ["KASPI_TOKEN"],
    "Accept": "application/vnd.api+json",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
                  "(KHTML, like Gecko) Chrome/124.0 Safari/537.36",
}
WAPI_HEADERS = {"Authorization": "Bearer " + os.environ["WAPI_KEY"]}
FROM = os.environ.get("WAPI_FROM")              # id номера из GET /numbers, если номеров несколько
PHONE_FIELD = os.environ["KASPI_PHONE_FIELD"]   # поле с телефоном покупателя из заказа (сверьте с документацией Kaspi)
SHOP = "Ваш магазин"
TEXTS = {
    "PICKUP": "Здравствуйте! Это магазин «{shop}» на Kaspi. Заказ №{code} принят. Самовывоз: "
              "Алматы, ул. Абая, 10, с 10:00 до 20:00. Вопросы по заказу — ответьте на это сообщение.",
    "DELIVERY": "Здравствуйте! Заказ №{code} из магазина «{shop}» передан курьеру, "
                "он позвонит перед приездом. Вопросы по заказу — пишите сюда.",
}
STOP = {"daily_limit", "new_contacts_limit", "cold_blocked", "spam_detected", "number_paused", "not_connected"}

db = sqlite3.connect("kaspi_notified.db")
db.execute("CREATE TABLE IF NOT EXISTS notified (order_id TEXT, state TEXT, PRIMARY KEY (order_id, state))")


def kaspi_orders(state, days=7):
    now = int(time.time() * 1000)
    since = now - days * 86_400_000             # дата создания — в миллисекундах
    orders, page = [], 0
    while True:
        url = (f"{KASPI}/orders?page%5Bnumber%5D={page}&page%5Bsize%5D=100"
               f"&filter%5Borders%5D%5Bstate%5D={state}"
               f"&filter%5Borders%5D%5BcreationDate%5D%5B$ge%5D={since}"
               f"&filter%5Borders%5D%5BcreationDate%5D%5B$le%5D={now}")
        r = requests.get(url, headers=KASPI_HEADERS, timeout=30)
        r.raise_for_status()
        body = r.json()
        orders += body.get("data", [])
        page += 1
        if page >= body.get("meta", {}).get("pageCount", 1):
            return orders


def to_phone(raw):
    d = "".join(ch for ch in str(raw or "") if ch.isdigit())
    if len(d) == 10:
        d = "7" + d
    elif len(d) == 11 and d.startswith("8"):
        d = "7" + d[1:]
    return d if len(d) == 11 and d.startswith("7") else None


def wapi(path, payload):
    if FROM:
        payload["from"] = int(FROM)
    r = requests.post(WAPI + path, json=payload, headers=WAPI_HEADERS, timeout=15)
    return r.status_code, r.json()


def mark(order_id, state):
    db.execute("INSERT OR IGNORE INTO notified VALUES (?, ?)", (order_id, state))
    db.commit()


def main(init):
    if not init and not 9 <= datetime.now(ZoneInfo("Asia/Almaty")).hour < 21:
        return                                  # ночью не пишем
    for state, template in TEXTS.items():
        for order in kaspi_orders(state):
            oid, a = order["id"], order.get("attributes", {})
            if db.execute("SELECT 1 FROM notified WHERE order_id = ? AND state = ?", (oid, state)).fetchone():
                continue
            phone = to_phone((a.get("customer") or {}).get(PHONE_FIELD))
            if init or not phone:
                mark(oid, state)
                continue
            status, check = wapi("/contacts/check", {"phone": phone})
            time.sleep(1)                       # проверки — не чаще одной в секунду
            if status != 200:
                continue                        # проверка не прошла — попробуем в следующий запуск
            if not check.get("exists"):
                mark(oid, state)                # WhatsApp нет — статус покупатель увидит в Kaspi
                continue
            status, res = wapi("/messages/send", {"to": phone, "text": template.format(shop=SHOP, code=a.get("code"))})
            code = res.get("code")
            if status == 200 or code in ("invalid_to", "empty_text", "text_too_long"):
                mark(oid, state)                # отправлено или номер негодный — повтор не поможет
            elif code in STOP or status in (400, 401, 403):
                print("Стоп:", code, res.get("error"))   # лимит, пауза, from_required, неверный ключ
                return
            time.sleep(5)                       # первые сообщения новым людям — не чаще раза в 5 секунд


if __name__ == "__main__":
    main("--init" in sys.argv)

Упёрлись в лимит или номер на паузе — скрипт останавливается и подхватит оставшиеся заказы в следующий запуск. Чтобы узнавать об этом сразу, добавьте оповещение в Telegram.

Ответы покупателей

Покупатели отвечают: «можно забрать завтра?», «курьер не позвонил». Номер остаётся на телефоне, поэтому ответ сразу виден в WhatsApp у менеджера, и ответить можно оттуда же. В журнале сообщение покупателя помечено source: "contact", а ответ менеджера с телефона — source: "phone".

Если менеджеров несколько, задайте вебхук: событие message с текстом и номером покупателя можно переслать в рабочий чат или CRM — см. гайд про вебхуки. Не отвечайте на вопросы шаблоном: человек спросил — ему нужен человек.

Если покупатель пришлёт фото, например повреждённой упаковки, в вебхуке будет отметка hasMedia: true, а сам снимок через несколько секунд появится в истории — ссылка на него в поле media.url сообщения GET /messages/{id}. И, конечно, его видно на телефоне.

Лимиты и первые дни

Почти каждое такое уведомление — первое сообщение новому человеку, а для них свои лимиты:

  • первые 2 недели после подключения — прогрев: дни 1–3 — до 20 новых контактов в сутки, дни 4–7 — до 40, дни 8–14 — до 80;
  • дальше на уровне «Хороший» — 150 новых контактов и 1 000 сообщений в сутки;
  • первые сообщения новым людям — не чаще раза в 5 секунд;
  • если один и тот же текст за час ушёл 25+ новым людям или за час появилось 40+ новых контактов, первые сообщения новым людям приостанавливаются на 24 часа. Текст, в котором меняется только номер заказа, для этой защиты — один и тот же.

Если заказов больше лимита, не пишите на каждом шаге — оставьте одно самое полезное сообщение. Если поток заказов постоянно упирается в лимиты, это сигнал смотреть на официальный WhatsApp Business API, а не раскидывать уведомления по нескольким номерам: лимиты и защита от рассылок действуют на каждом номере.

Платы за сообщения нет: номер стоит 9 900 ₸ в месяц, сколько бы уведомлений ни ушло в пределах лимитов. Подробнее — на странице цен.

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

Почему опрос по таймеру, а не вебхук от Kaspi?

Вебхуков для продавцов у Kaspi нет, заказы приходится запрашивать самим. Опроса раз в 5–10 минут хватает: сообщение уходит вскоре после смены состояния.

Можно ли писать покупателям Kaspi про акции и новинки?

Нет, только по оформленному заказу. Реклама и увод с площадки ставят под удар ваш магазин на Kaspi и повышают риск блокировки номера.

Что, если у покупателя нет WhatsApp?

Скрипт проверяет номер через /contacts/check (до 200 проверок в сутки на номер) и пропускает такие заказы. Статус покупатель всё равно видит в приложении Kaspi.

Гарантируете, что номер не заблокируют?

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

Можно ли использовать основной номер магазина?

Да. Номер остаётся на телефоне и работает как обычно, а скрипт пишет с него через API. Удобно, что ответы покупателей менеджер видит в привычном WhatsApp.

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

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