Metadata-Version: 2.4
Name: waix-python
Version: 0.2.0
Summary: WAIX WhatsApp API v1: messages, templates, OTP and signed webhooks
Author: WAIX
License-Expression: MIT
Project-URL: Homepage, https://waix.kz/integrations/python
Project-URL: Repository, https://github.com/ivanpukhov/waix-python
Project-URL: Issues, https://github.com/ivanpukhov/waix-python/issues
Keywords: waix,whatsapp,otp,kazakhstan
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# waix-python — WhatsApp API для Python

[English](https://github.com/ivanpukhov/waix-python/blob/main/README.en.md)

Клиент WAIX для Python 3.10 и новее. Работает на стандартной библиотеке, содержит аннотации типов. Запросы синхронные: в асинхронном веб-приложении вызывайте SDK через рабочий поток.

## Установка

```sh
python -m pip install "waix-python @ git+https://github.com/ivanpukhov/waix-python.git@v0.2.0"
```

[Исходный код](https://github.com/ivanpukhov/waix-python)

## Перед первым запросом

Подключите номер в WAIX, получите Connection ID и серверный ключ с правом `messages:write`. Пример использует одобренный шаблон `order_ready` на русском языке с одной переменной в теле.

## Отправка шаблона

```python
import os
import uuid
from waix import Waix, WaixError

waix = Waix(os.environ['WAIX_API_KEY'])
# Создайте и сохраните UUID вместе с событием заказа до отправки.
event_id = str(uuid.uuid4())
result = waix.messages.send({
    'connection_id': os.environ['WAIX_CONNECTION_ID'],
    'to': '+77071234567', 'type': 'template',
    'template': {
        'name': 'order_ready', 'language': {'code': 'ru'},
        'components': [{'type': 'body', 'parameters': [{'type': 'text', 'text': '42'}]}],
    },
}, event_id)
print(result['data']['id'], result['data']['status'])
```

## OTP: отправка и проверка

```python
otp = Waix(os.environ['WAIX_OTP_PROJECT_KEY'])
sent = otp.otp.send({'to': '+77071234567', 'ttl': 300}, event_id)
# Сохраните sent['data']['id'] в серверной сессии пользователя.
status = otp.otp.status(sent['data']['id'])
# supplied_code — код, введённый пользователем в той же сессии.
verified = otp.otp.verify(sent['data']['id'], supplied_code)
```

## Ошибки, файлы и параметры клиента

Перехватывайте `WaixError`. Поля: `status` (0 при сетевой ошибке), `code`, `request_id`, `retry_after`, `body`. Таймаут задаётся в секундах:

```python
waix = Waix(os.environ['WAIX_API_KEY'], timeout=30, base_url='https://waix.kz/api/v1')
```

Проверка TLS-сертификатов включена. Если локальный Python не находит доверенный сертификат, настройте хранилище CA операционной системы или Python; не отключайте проверку TLS.

Загрузка файла: `waix.media.upload(connection_id, 'invoice.pdf', content_type='application/pdf', type='document')`. Файл читается в память; предел SDK — 100 МБ, дополнительно действуют ограничения API для типа файла.

Пагинация: `waix.messages.list(limit=50, before=cursor, before_id=cursor_id)`.

Проверка вебхука: `verify_webhook(raw_body, timestamp_header, signature_header, secret)`; функция импортируется из `waix`.

## Разработка

```sh
PYTHONPATH=src python -m unittest discover -s tests
python -m pip install build
python -m build
```

Тесты используют локальный HTTP-сервер и не отправляют сообщения клиентам.

## Какие методы есть

| Раздел | Возможности |
| --- | --- |
| Сообщения | Отправка, список с пагинацией, просмотр, явный повтор |
| Подключения | Список номеров, чтение и изменение профиля компании |
| Шаблоны | Список, просмотр, создание, изменение, удаление, предварительный просмотр |
| Медиа | Загрузка файла, список, получение URL, удаление |
| Вебхуки | Чтение и изменение настроек, тест, удаление, смена секрета |
| OTP | Отправка кода, проверка, статус запроса |

Все запросы идут на `https://waix.kz/api/v1`. SDK возвращает полный JSON-ответ: `data`, а также `pagination`, если она есть. Для остальных операций API v1 можно использовать метод `request` с относительным путём. Не передавайте в него адрес, полученный от непроверенного пользователя.

Некоторым операциям нужны права управления и ключ компании. Ключ отдельного OTP-проекта не даёт доступа к настройкам вебхука компании. Список прав и полей: [спецификация API](https://waix.kz/openapi-api-v1.json).

## Повторные запросы и доставка

Создайте UUID один раз при записи события в своей базе. Передайте его как ключ идемпотентности. При потере ответа повторяйте запрос с **тем же UUID и теми же параметрами**: новый ключ означает новое сообщение.

HTTP `202` означает, что сообщение поставлено в очередь. Доставку проверяйте по вебхуку, журналу WAIX или методу просмотра сообщения. У SDK нет автоматических повторов и переходов по HTTP redirect. Для `429` учитывайте `Retry-After`; ошибки `400`, `401`, `403` требуют исправления параметров или доступа. Сообщение со статусом `outcome_unknown` нельзя повторять вслепую.

## Проверка подписи вебхука

Передавайте в функцию проверки **исходные байты тела HTTP-запроса до разбора JSON**, заголовки `X-Waix-Timestamp`, `X-Waix-Signature` и секрет вебхука. Подпись: HMAC-SHA256 от `timestamp + "." + rawBody`, с префиксом `v1=`. Сравнение выполняется за постоянное время; допустимое отклонение времени по умолчанию — 300 секунд.

Отклоняйте неверную подпись. Повторные события определяйте по `X-Waix-Delivery`: верная подпись сама по себе не защищает от повторной доставки в пределах допустимого времени. Сначала надёжно сохраните событие, затем ответьте кодом `2xx`.

## Ключи, OTP и данные клиентов

- Храните ключи на сервере. Не включайте их в код сайта, мобильного приложения или общий файл сценария. Выдавайте только нужные права.
- Для первого сообщения клиенту обычно нужен одобренный шаблон. Произвольный текст разрешён в рамках действующего окна обслуживания Meta. Проверяйте согласие клиента и учитывайте отказ от сообщений.
- OTP использует отдельный ключ проекта. Начните с sandbox: он возвращает `test_code` и не отправляет сообщение WhatsApp. Тестовый код нельзя показывать человеку, чью личность вы проверяете.
- Сохраните ID OTP-запроса в серверной сессии пользователя. Проверять код должна именно эта сессия. Выдавайте доступ только после успешной проверки; ограничивайте попытки по аккаунту и IP.
- Для рабочих OTP нужны доступный тариф и одобренный отправитель. Проверьте их состояние в кабинете WAIX до включения реальной отправки.
- SDK не записывает ключи, сообщения и коды в лог. Если добавляете свои логи, скрывайте эти данные и сохраняйте `request_id` для диагностики.

## Документация и поддержка

[Документация WAIX](https://waix.kz/docs) · [Поддержка](https://waix.kz/contacts) · [Тарифы](https://waix.kz/pricing).

SDK работает с API v1 WAIX. Это не клиент Meta Graph API. Версии SDK следуют SemVer. Лицензия — MIT.

## Поведение версии 0.2.0

SDK не повторяет запрос за вас. HTTP-ошибка от прокси остаётся HTTP-ошибкой, даже если вместо JSON пришёл HTML: сохраняются статус, request ID и `Retry-After`. Перенаправления запрещены. Некорректный успешный ответ вызывает `INVALID_RESPONSE`; ответ больше установленного лимита — `RESPONSE_TOO_LARGE`. Лимит по умолчанию — 2 МиБ, его можно увеличить до 16 МиБ.

| Ситуация | Что делать |
| --- | --- |
| `400` / `422` | Исправить поля, формат телефона, шаблон или код OTP. |
| `401` / `403` | Проверить ключ, права и принадлежность подключения/OTP-проекта. |
| `409` | Проверить конфликт ключа идемпотентности: под одним ключом нельзя менять тело. |
| `429` | Отложить запрос на срок из `Retry-After`, сохранив прежний ключ и тело. |
| `5xx`, `TIMEOUT`, `TRANSPORT_ERROR` | Результат отправки может быть неизвестен. Сначала проверить сохранённый ID; если ID не получен, повторять прежний запрос с прежним ключом через ограниченную очередь повторов. |
| `INVALID_RESPONSE` / `RESPONSE_TOO_LARGE` | Проверить прокси, адрес API и размер страницы. Не создавать новую отправку. |
| `OTP_INVALID` | Код неверен, истёк или уже использован. Не выдавать сессию приложения. |

`body` исключения доступен для диагностики, но может содержать данные клиента. В журнал записывайте только безопасные метаданные из примера ниже. Не сериализуйте целиком ответ sandbox OTP.

### Обновление с 0.1.x

Имена существующих методов сохранены. У `otp.verify` код должен быть строкой из шести цифр: `'012345'`, а не число. Значения query — только строки, конечные числа и boolean; сложные объекты нужно разобрать на параметры. Ошибки HTML от прокси теперь имеют `API_ERROR`, а перенаправления — `REDIRECT_DISALLOWED`. При обработке ошибок ориентируйтесь также на HTTP-статус.

### Пагинация и диагностика

```python
waix = Waix(os.environ['WAIX_API_KEY'], timeout=30, max_response_bytes=2097152)
for message in waix.messages.iterate(connection_id=connection_id, limit=100, max_pages=100):
    save_status(message['id'], message['status'])
```

Генератор запрашивает страницы по мере чтения, сохраняет фильтры и передаёт оба курсора. Повторный курсор вызывает `INVALID_PAGINATION`; достижение `max_pages` — `PAGINATION_LIMIT`. По умолчанию предел — 1000 страниц. `break` останавливает загрузку следующих страниц.

```python
try:
    result = waix.messages.get(saved_message_id)
except WaixError as error:
    logger.warning('WAIX request failed', extra=error.to_dict())
    delay_seconds = error.retry_delay()  # секунды или None
    # Решение о повторе принимает ваша очередь.
```

Python-клиент синхронный. `timeout` задаёт таймаут сетевых операций urllib, а не общий дедлайн задания. В асинхронном сервере запускайте клиент в отдельном потоке или очереди; не блокируйте event loop. Файл multipart загружается в память; для больших файлов выделите отдельный worker.
