Відповіді для розробників інтеграцій: відправлення та статуси повідомлень, ідемпотентність, сесії Viber, вебхуки, розсилки та оплата.
HTTP 200 означає, що Direct Hub прийняв повідомлення. У відповіді приходить { id, status }, де status = WaitingToSend.
MessageSendError.application/problem+json з полями title, status, detail, traceId (див. HTTP-помилки під час відправлення).Кожному етапу відповідає таке:
| Етап | Що надходить від Direct Hub |
|---|---|
| Прийнято Direct Hub | HTTP 200, { id, status }, status = WaitingToSend |
| Відправлено / прийнято Viber | вебхук MessageSent |
| Доставлено | вебхук MessageDelivered |
| Прочитано | вебхук SeenMessage |
| Помилка | вебхук MessageSendError, причина в полі error |
| Строк доставки минув | вебхук MessageExpired |
Статус у відповіді на відправлення — WaitingToSend. Далі приходять події:
| Подія | Остаточна |
|---|---|
MessageSent | ні |
MessageDelivered | успіх. SeenMessage може прийти пізніше |
SeenMessage | так |
MessageSendError | так, помилка |
MessageExpired | так, не доставлено |
MessageDelivered повідомлення не слід показувати як доставлене.MessageDelivered може прийти кілька разів, якщо в отримувача Viber встановлено на кількох пристроях.Строк доставки — 10 хвилин. Повідомлення, не доставлене за цей час, отримує MessageExpired.
error у MessageSendError?BadRecipient, RecipientNotRegistered, RecipientBlockedMessages, DoesNotSupportDevice, AttemptExceededSession, BadParameters та інші;Network error has occurred., Timeout has occurred., Unsuccessful status code..| Код | Значення | Повторювати? |
|---|---|---|
400 | помилка валідації, немає активної сесії, сесію призупинено | ні, потрібно виправити запит або умову |
402 | недостатньо коштів або вичерпано пакет | ні, до поповнення або нового періоду |
403 | компанію заблоковано або не призначено тариф | ні |
404 | ресурс не знайдено | ні |
409 | запит із цим idempotencyKey ще виконується | так, через кілька секунд із тим самим ключем |
422 | idempotencyKey уже використано для іншого отримувача | ні, потрібен новий ключ |
429 | перевищено ліміт запитів | так, після Retry-After |
503 | ресурс тимчасово зайнятий | так, із тим самим idempotencyKey |
500 | внутрішня помилка | повторіть пізніше; якщо помилка повторюється — зверніться до підтримки |
Наразі API не дає змоги:
messageId;trackingData;Стан потрібно накопичувати у себе за вебхуками. Події, пропущені під час недоступності вашого сервера, повторно не доставляються (див. доставку вебхуків).
Дізнатися поточний статус конкретного повідомлення можна одним способом: повторити запит на відправлення з тим самим idempotencyKey (див. Ідемпотентність і повтори). Нове повідомлення при цьому не створюється.
idempotencyKey?Повторний запит із тим самим ключем повертає HTTP 200 з тим самим id і поточним status першого повідомлення, а також заголовок Idempotent-Replayed: true. Нове повідомлення не створюється, оплата повторно не списується, сесійний ліміт не витрачається.
Ключ шукається серед повідомлень того самого бізнес-номера, тобто того самого API-токена. Якщо повідомлення з таким ключем знайдено, порівнюється тільки отримувач:
id, поточний status і заголовок Idempotent-Replayed: true;Інші поля запиту (текст, шаблон і його параметри, зображення або файл, trackingData) не порівнюються. Запит із тим самим ключем і тим самим отримувачем, але з іншим текстом, поверне вже створене повідомлення, а нове не буде відправлено. Тому для кожного нового повідомлення використовуйте новий ключ.
Ні. Повтор не відправляє повідомлення заново, навіть якщо перше завершилося помилкою: ключ означає «те саме повідомлення». Щоб відправити заново, використайте новий ключ.
Якщо перший запит було відхилено до створення повідомлення (коди 400, 402, 403, 503), повідомлення з цим ключем немає, і повтор із ним виконується як нова спроба.
Той самий ключ з іншим отримувачем відхиляється з кодом 422.
Одночасні запити з одним ключем безпечні: буде створено одне повідомлення.
Якщо перший запит із цим ключем ще виконується довше 2 секунд (наприклад, завантажується великий файл), повтор отримає 409. Повторіть запит через кілька секунд: прийде результат першого запиту із заголовком Idempotent-Replayed: true.
Генеруйте новий ключ для кожного нового повідомлення і повторюйте запит із тим самим ключем, якщо відповідь не отримано (таймаут, обрив з'єднання) або отримано 503.
Рекомендовані інтервали повторів — див. З якими інтервалами повторювати запити?, а дії для кожного коду — у таблиці HTTP-помилок.
+380XXXXXXXXX, із плюсом. Наразі підтримуються лише номери України.
У тому вигляді, в якому його передає Viber, із плюсом.
Ні, такої попередньої перевірки немає.
MessageSendError, error = "BadRecipient";"RecipientNotRegistered";"RecipientBlockedMessages";"DoesNotSupportDevice".Ліміти діють на IP-адресу клієнта: для IPv4 — на конкретну адресу, для IPv6 — на підмережу /64. Методи поділено на групи, і в кожної групи свій лічильник:
| Група | Ліміт |
|---|---|
Відправлення повідомлень (/api/message/...) | 1200 запитів на хвилину |
| Решта методів API | 300 запитів на хвилину |
Одразу повертається HTTP 429 із заголовком Retry-After (через скільки секунд відкриється нове вікно). Запит не виконується і не тарифікується.
Retry-After;idempotencyKey.Використовуйте розсилки: вони не витрачають ліміт на відправлення повідомлень.
Обмеження Bot API до Direct Hub не застосовуються. Діють такі:
| Параметр | Обмеження |
|---|---|
| Текст повідомлення | до 1000 символів |
trackingData | від 3 до 256 символів |
| Файл (сесійне відправлення) | до 99 МБ, назва файлу разом із розширенням — до 25 символів |
| Зображення | до 50 МБ |
| Підпис до сесійного зображення | не підтримується: сесійне зображення відправляється без тексту |
| Текст кнопки промоповідомлення | до 30 символів |
Так. Сесію відкриває будь-яке вхідне повідомлення від користувача, зокрема відповідь на розсилку.
Ні. Вікно фіксоване: 24 години від першого вхідного. Нове вхідне не продовжує його і не скидає ліміт у 60, але знімає паузу після 10 вихідних поспіль без відповіді.
Сесійні відправлення (текст, зображення, файл), прийняті Direct Hub. Місце в ліміті витрачається в момент прийняття і не повертається, навіть якщо Viber потім відмовить. Платні повідомлення та повтори з тим самим idempotencyKey у ліміт не входять.
API для цього немає. Залишок рахується так: 60 мінус кількість прийнятих сесійних відправлень з моменту події SessionOpened.
SessionClosed (Expired) може прийти із затримкою до кількох хвилин, але відправлення в сесію, що минула, відхиляється одразу.SessionClosed (MessageLimitReached).Причини закриття сесії (closeReason):
Expired — минули 24 години;MessageLimitReached — вичерпано ліміт у 60 повідомлень;ViberRejected — Viber повідомив, що сесію на його боці вже закрито.Проходять рівно 60. Решта отримують 400 або йдуть платно, якщо ввімкнено автоперехід (див. автоматичний перехід до платного відправлення).
Viber може відхилити повідомлення. Тоді прийдуть MessageSendError (AttemptExceededSession) і SessionClosed (ViberRejected). Відхилене повідомлення не тарифікується.
Автоперехід вмикається прапорцем у запиті або налаштуванням номера. Для нових номерів налаштування номера ввімкнене за замовчуванням. Тому false або відсутність прапорця самі по собі не забороняють платне відправлення. Щоб його виключити, зверніться до нас із проханням вимкнути налаштування на вашому номері.
Автоперехід спрацьовує лише тоді, коли безкоштовне відправлення неможливе, зокрема під час паузи через 10 повідомлень без відповіді. За інших помилок він не спрацьовує.
Повертається 400:
No active Viber session — сесії немає, її закрито або вичерпано ліміт;Viber session paused due to unanswered outbound messages limit — відправлено 10 повідомлень поспіль без відповіді.В обох випадках нічого не списується.
Для зображень є. /api/message/session/image приймає той самий прапорець fallbackToPromotional, і на нього так само діє налаштування номера. Без сесії зображення йде платним промоповідомленням «лише зображення» за тарифом цього типу.
Для файлів немає: у Viber немає промоповідомлення з файлом. Без сесії /api/message/session/file повертає 400 незалежно від налаштування номера.
Вебхук — єдиний механізм отримання подій.
Direct Hub чекає на відповідь 2xx 10 секунд і робить одну спробу без повторів. Події, пропущені під час недоступності вашого сервера, повторно не доставляються.
Кожен вебхук — це POST із JSON-тілом і двома заголовками:
| Заголовок | Значення |
|---|---|
X-Webhook-Signature | sha256= і далі HMAC-SHA256 від тіла запиту у hex, нижній регістр |
X-Webhook-Event | тип події, збігається з полем eventType у тілі |
Як перевірити:
sha256=.X-Webhook-Signature функцією порівняння за сталий час. Якщо значення не збігаються, відхиліть запит і не обробляйте подію.Найчастіша помилка — використати як ключ сам рядок секрету. Ключ HMAC — це байти, отримані декодуванням hex-секрету. З рядком як ключем підпис не збігатиметься ніколи.
Інші причини розбіжності:
Node.js
const crypto = require('crypto');
// rawBody — Buffer із сирим тілом запиту (наприклад, express.raw({ type: 'application/json' }))
function verifyWebhook(rawBody, signatureHeader, secretHex) {
const expected = 'sha256=' + crypto
.createHmac('sha256', Buffer.from(secretHex, 'hex'))
.update(rawBody)
.digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader || '');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Python
import hashlib
import hmac
def verify_webhook(raw_body: bytes, signature_header: str, secret_hex: str) -> bool:
expected = "sha256=" + hmac.new(bytes.fromhex(secret_hex), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature_header or "")
C#
using System.Security.Cryptography;
using System.Text;
static bool VerifyWebhook(byte[] rawBody, string? signatureHeader, string secretHex)
{
var hash = HMACSHA256.HashData(Convert.FromHexString(secretHex), rawBody);
var expected = "sha256=" + Convert.ToHexString(hash).ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(
Encoding.ASCII.GetBytes(expected),
Encoding.ASCII.GetBytes(signatureHeader ?? ""));
}
PHP
// $rawBody = file_get_contents('php://input');
// $signatureHeader = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
function verifyWebhook(string $rawBody, string $signatureHeader, string $secretHex): bool
{
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, hex2bin($secretHex));
return hash_equals($expected, $signatureHeader);
}
secret відповіді на створення. Це рядок із 64 hex-символів (32 випадкові байти).Як перевіряти підпис — див. Як перевірити підпис вебхука?
Користувач може надіслати фото, відео або файл. У вебхуку NewMessage приходять поля fileUrl і fileName. Посилання веде на сервер Viber. За документацією Viber файл зберігається до 7 днів, після цього недоступний, і відновити його не можна.
Рекомендуємо завантажувати файл одразу після отримання події.
До 200 000 отримувачів. Використовуються лише схвалені промошаблони. Параметри шаблону задаються один раз на всю розсилку, персоналізації за отримувачем немає.
Ні. Розсилка отримує id Direct Hub.
added і skippedDuplicates;Ні, планування, пауза та скасування не підтримуються.
У розсилки доступний лише загальний стан: Configuration, ReadyToSend, InProgress, Completed, Abandoned. Статуси окремих повідомлень і вебхуки доставки для розсилок не надаються.
Completed?Відправлення всім отримувачам завершено. Це не означає, що всі повідомлення доставлено.
Під час відправлення сума резервується. Списується вона, коли Viber прийняв повідомлення (MessageSent).
У разі MessageSendError резерв повертається. Повідомлення з MessageExpired оплачене, бо Viber його прийняв.
Пакет видається 1-го числа за київським часом. Невикористаний залишок минулого місяця згорає.
Через API-токен таких методів немає. Залишок та історія операцій доступні в особистому кабінеті.
За API-токеном можна лише отримати список шаблонів, за бажанням із фільтром за типом. Створення шаблону та надсилання на модерацію доступні в особистому кабінеті.