Перейти к содержанию
Версия контракта: 0.3.3 — спека

Статусы и их проверка

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

Модель статусов

accepted ──▶ sent ──▶ delivered
    │          │  └──▶ undelivered
    │          │  └──▶ expired
    │          └─────▶ rejected
    ├─────────────────▶ rejected
    └─────────────────▶ expired
  • accepted — хаб принял сообщение и отвечает за него. Это не отправка.
  • sent — оператор принял сообщение в работу.
  • delivered — оператор подтвердил доставку абоненту.
  • undelivered — оператор сообщил, что доставить не удалось; причина в reason_code.
  • expired — срок жизни истёк раньше доставки. Различаются три случая, см. ниже; кроме них, expired может выставить вручную администратор хаба (запись истории с source: admin).
  • rejected — сообщение отвергнуто: оператором на приёме или нашими проверками.

Поле final: true означает, что статус больше не изменится — даже вручную. После final меняются только отметки, не статус: поздний отчёт о доставке добавляет delivered_late_at и уточняет списание (событие message.delivered_late), прочтение у delivered — seen_at.

Переход sent → rejected законен: оператор вправе отвергнуть уже принятое сообщение отчётом.

Прочтение статусом не является. Оно фиксируется полем seen_at и событием message.seen: иначе final: true перестал бы что-либо означать.

Четыре способа узнать статус

  1. GET /v1/messages/{id} — одно сообщение целиком, вместе с status_history[]: каждая запись несёт status, at, source и, если есть, reason_code с пояснением reason_message. source — кто применил статус: gate (проверки на приёме; первая запись accepted — всегда gate), provider (оператор), reaper (наш срок жизни), late_dlr (поздний отчёт), sandbox (симулятор песочницы), admin (ручной перевод).
  2. GET /v1/messages?status=&to=&batch=&client_ref=&created_after=&created_before= — выборка по фильтрам, страницами.
  3. GET /v1/events?starting_after=&created_after=&type= — журнал событий, он же способ догнать пропущенное после простоя приёмника. Курсор — идентификатор события (evt_…), а не время: время не уникально.
  4. Вебхуки — события приезжают сами. Это основной способ; журнал нужен, когда приёмник лежал.

Хаб, кроме того, сам спрашивает оператора о молчащих сообщениях (сверка статусов): одноразовые коды — через минуту, сервисные — через десять. Отдельно делать это интегратору не нужно.

Что делать с expired

Три причины, и они означают разное (плюс ручной перевод администратором — source: admin, причина может отсутствовать):

reason_code Что случилось Что делать
ttl_expired_before_submit Срок жизни истёк, пока сообщение стояло в очереди: оператору оно не уходило, денег за него не берём Отправить заново, если код всё ещё нужен
dlr_timeout_local Оператор принял сообщение, но отчёта не прислал до истечения нашего срока Отправлять заново нельзя вслепую: сообщение могло дойти. Ждать позднего отчёта — только для этой причины он будет принят (message.delivered_late)
dlr_timeout Оператор сам сообщил, что перестал ждать доставки (EXPIRED от оператора) Доставки уже не будет, позднего отчёта не придёт. Повторить позже, если сообщение ещё нужно

Одноразовые коды автоматически переотправлять не следует: абонент, получивший два кода подряд, вводит первый. Для сервисных сообщений повтор допустим, но не раньше, чем через интервал, согласованный с продуктом.

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

Таблица генерируется из реестра в коде: совет в ней — часть контракта, а не пожелание.

reason_code Что произошло Что делать
undelivered_unknown Не доставлено, оператор не сообщил причину. повторить позже
recipient_unknown Номер не существует. не повторять
handset_unreachable Аппарат недоступен. повторить позже
recipient_not_serviced Абонент не обслуживается оператором. не повторять
sms_not_provisioned На номере не подключён приём SMS. не повторять
handset_error Аппарат не смог принять сообщение. повторить позже
recipient_blocked Номер заблокирован оператором. не повторять
handset_memory_full Память аппарата переполнена. повторить позже
recipient_temporarily_blocked Номер временно заблокирован. повторить позже
spam_filter_operator Заблокировано антиспам-фильтром оператора. не повторять
spam_filter_recipient Заблокировано фильтром получателя. не повторять
spam_filter_content Содержимое сообщения не прошло фильтр. исправить сообщение и отправить заново
spam_filter_sender Отправитель не прошёл фильтр. не повторять
operator_internal_error Внутренняя ошибка оператора. повторить позже
fraud_blocked Заблокировано антифрод-системой. не повторять
sender_blocked_by_recipient Получатель заблокировал этого отправителя. не повторять
messenger_app_missing У получателя нет приложения мессенджера. не повторять
dlr_timeout Оператор не доставил сообщение до истечения срока ожидания. повторить позже
dlr_timeout_local Срок жизни сообщения истёк, отчёт о доставке не получен. исход неизвестен: повтор рискует дублем, дождаться позднего отчёта
recipient_address_invalid Оператор счёл номер получателя непригодным. исправить сообщение и отправить заново
duplicate_at_provider Оператор отверг сообщение как дубликат. не повторять
message_too_long Оператор счёл текст сообщения слишком длинным. исправить сообщение и отправить заново
provider_submit_failed Не удалось передать сообщение оператору. повторить позже
ttl_expired_before_submit Срок жизни истёк до отправки, оператору сообщение не передавалось. повторить позже
opted_out_before_send Получатель отписался до отправки сообщения. не повторять

Причины, относящиеся к нашему подключению у оператора (реквизиты, лимиты, баланс), интегратору не показываются: он получает provider_submit_failed, а разбирается с этим дежурный.