Подписка — копия ленты уведомлений канала по своему адресу. Очередь и вебхук канала при этом не меняются: основное приложение читает свою ленту, а CRM, аналитика или архив получают ту же ленту отдельно. Каждую доставку Gratio подписывает.
| Создать или изменить | POST /waInstance{idInstance}/subscriptions/{apiTokenInstance} |
| Список | GET /waInstance{idInstance}/subscriptions/{apiTokenInstance} |
| Снять | DELETE /waInstance{idInstance}/subscriptions/{apiTokenInstance}/{subscriptionId} |
| Тело | JSON |
| Каналы | MAX, Telegram, WhatsApp, почта |
Те же запросы работают по адресу /v1/instances/{idInstance}/subscriptions с заголовком Authorization: Bearer {apiTokenInstance}.
Параметры
Тело POST:
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
name |
string | да | имя подписки: латиница, цифры, ., _, -, до 64 символов. Имена на gratio- заняты |
url |
string | да | куда доставлять: https:// или http://, до 500 символов, публичный хост |
secret |
string | нет | секрет подписи, 16–128 символов. Не передали — Gratio создаст его и вернёт один раз в ответе |
Подписка с таким name уже есть — POST меняет её адрес и секрет, очередь доставки сохраняется. POST без secret выдаёт новый секрет: подпись старым перестаёт сходиться сразу.
Пример запроса
Подписать CRM на ленту канала, секрет создаст Gratio:
curl
curl -X POST "$GRATIO_URL/waInstance$GRATIO_ID/subscriptions/$GRATIO_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"name": "crm",
"url": "https://crm.example.ru/hooks/gratio"
}'
Python
import os
import requests
BASE = f"{os.environ['GRATIO_URL']}/waInstance{os.environ['GRATIO_ID']}"
TOKEN = os.environ["GRATIO_TOKEN"]
url = f"{BASE}/subscriptions/{TOKEN}"
payload = {"name": "crm", "url": "https://crm.example.ru/hooks/gratio"}
response = requests.post(url, json=payload, timeout=60)
print(response.status_code, response.json())
Node.js
const { GRATIO_URL, GRATIO_ID, GRATIO_TOKEN } = process.env;
const base = `${GRATIO_URL}/waInstance${GRATIO_ID}`;
const url = `${base}/subscriptions/${GRATIO_TOKEN}`;
const payload = { name: "crm", url: "https://crm.example.ru/hooks/gratio" };
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
console.log(response.status, await response.json());
PHP
<?php
$base = getenv('GRATIO_URL') . '/waInstance' . getenv('GRATIO_ID');
$url = $base . '/subscriptions/' . getenv('GRATIO_TOKEN');
$payload = ['name' => 'crm', 'url' => 'https://crm.example.ru/hooks/gratio'];
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
]);
$response = json_decode(curl_exec($ch), true);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), PHP_EOL;
print_r($response);
Пример ответа
Ответ 200 OK:
{
"id": "sub_9f2c41d07be35a1c86e4f0b2",
"name": "crm",
"url": "https://crm.example.ru/hooks/gratio",
"createdAt": 1791823700,
"pending": 0,
"oldestPendingAt": null,
"dead": 0,
"lastDeliveredAt": null,
"lastError": null,
"lastErrorAt": null,
"system": false,
"created": true,
"secret": "a39e8979effc76a1915fb07c5e3b1d7ada556f9f12974f89312a3486ebe83095"
}
Сохраните secret сразу: Gratio показывает его только в этом ответе.
Поля ответа
| Поле | Тип | Описание |
|---|---|---|
id |
string | id подписки — для DELETE и заголовка X-Gratio-Subscription |
name, url |
string | имя и адрес доставки |
createdAt |
integer | когда подписку создали, секунды Unix |
pending |
integer | сколько уведомлений ждут доставки. В ответе POST — всегда 0, точное число — в списке |
oldestPendingAt |
integer или null | время самого старого недоставленного |
dead |
integer | сколько уведомлений подписка отвергла — отравленные |
lastDeliveredAt |
integer или null | последняя удачная доставка |
lastError, lastErrorAt |
string или null, integer или null | последняя причина неудачи (HTTP 500, ConnectTimeout, internal_host) и её время |
system |
boolean | true — служебная подписка Gratio: видна в списке, менять и снимать её ключом канала нельзя |
created |
boolean | true — подписку создали, false — изменили существующую |
secret |
string | секрет подписи; только если его создал Gratio |
Ещё примеры
Список подписок
Все подписки канала с очередью доставки:
curl "$GRATIO_URL/waInstance$GRATIO_ID/subscriptions/$GRATIO_TOKEN"
Ответ 200 OK — подписки по времени создания, со счётчиками доставки:
{
"subscriptions": [
{
"id": "sub_9f2c41d07be35a1c86e4f0b2",
"name": "crm",
"url": "https://crm.example.ru/hooks/gratio",
"createdAt": 1791823700,
"pending": 2,
"oldestPendingAt": 1791823905,
"dead": 0,
"lastDeliveredAt": 1791823890,
"lastError": "HTTP 503",
"lastErrorAt": 1791823906,
"system": false
}
]
}
Секрет в списке не показывается.
Свой секрет
Секрет задаёте сами — например, чтобы хранить его в своём хранилище секретов заранее:
curl -X POST "$GRATIO_URL/waInstance$GRATIO_ID/subscriptions/$GRATIO_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"name": "crm",
"url": "https://crm.example.ru/hooks/gratio",
"secret": "5d0c3f1a9b7e4c2d8a6f0e1b3c5d7e9f"
}'
В ответе нет поля secret.
Снять подписку
Передайте id подписки в конце пути:
curl -X DELETE "$GRATIO_URL/waInstance$GRATIO_ID/subscriptions/$GRATIO_TOKEN/sub_9f2c41d07be35a1c86e4f0b2"
Ответ 200 OK:
{"removed": true}
Недоставленные уведомления подписки удаляются вместе с ней.
Доставка
Gratio шлёт POST на url. Тело — уведомление целиком (как body в receiveNotification), компактный JSON в UTF-8.
| Заголовок | Значение |
|---|---|
Content-Type |
application/json |
X-Gratio-Signature |
t=<время>,v1=<подпись> — как проверить, см. Проверка подписи |
X-Gratio-Instance |
idInstance канала |
X-Gratio-Subscription |
id подписки |
X-Gratio-Delivery |
номер доставки; повтор приходит с тем же номером |
- Уведомления идут по одному, в порядке записи: следующее — после
2xxна текущее. Подписки канала доставляются независимо друг от друга. - Подписка получает уведомления, записанные после её создания.
- Набор уведомлений — тот же, что у очереди канала: флаги setSettings действуют и на подписки.
- Gratio ждёт ответа до 30 секунд.
| Ответ | Что сделает Gratio |
|---|---|
2xx |
доставлено |
408, 429, 5xx, тайм-аут, сбой сети |
повтор через 1, 5, 15, 60 и 300 секунд, дальше каждые 5 минут, пока уведомлению нет 24 часов; потом оно истекает |
3xx и другие 4xx |
повтор по тому же расписанию; набралось 5 неудачных попыток — уведомление уходит в отравленные подписки, очередь идёт дальше |
| адрес стал вести во внутреннюю сеть | как 4xx, причина — internal_host |
Отравленные подписки видно только счётчиком dead в списке: посмотреть и вернуть их пока нельзя. Доставленные, отравленные и истёкшие уведомления подписки хранятся 7 дней.
Повтор отсекайте по X-Gratio-Delivery или по eventId уведомления.
Ошибки
| Код | code |
Когда | Что делать |
|---|---|---|---|
400 |
validation |
нет name или url; имя не по правилам; url не http(s) или длиннее 500; secret короче 16 или длиннее 128; DELETE без id |
исправить запрос |
400 |
bad_url |
адрес ведёт во внутреннюю сеть | дать публичный адрес |
401 |
— | неверный idInstance или токен |
взять ключ в кабинете |
403 |
reserved |
имя на gratio- или служебная подписка Gratio |
выбрать другое имя |
404 |
not_found |
DELETE: подписки с таким id нет |
проверить id в списке |
409 |
limit |
у канала уже 5 подписок, служебные тоже считаются | снять ненужную |
Что дальше
- Проверка подписи — принимать доставку, только если подпись сошлась.
- Вторая система на той же ленте — подписка для CRM по шагам.
- Вебхук — основной приёмник уведомлений.
