← Все статьи
Руководство

Как отслеживать ошибки UnifyPort API через request_id и X-Request-Id

Для расследования ошибки UnifyPort API сохраните серверный request_id из JSON или заголовок ответа X-Request-Id. В запросе можно передать собственный X-Request-Id: JSON-ответ вернёт его как client_request_id, чтобы связать ответ с журналом приложения. Эти идентификаторы помогают найти запрос. Они не являются ID сообщения, ID события webhook или гарантией безопасного повторного выполнения.

Главное

  • Разделяйте бизнес-операцию, каждую HTTP-попытку и серверный ID запроса.
  • Читайте заголовки даже без JSON, в том числе после успешного удаления с ответом 204.
  • При тайм-ауте серверного ID может не быть, а результат операции может остаться неизвестным.
  • Записывайте структурированные коды ошибок, а не секреты и полное содержимое сообщений.

Какой ID запроса сохранять?

Контракт трассировки описан во введении в API. Один и тот же заголовок играет разные роли в запросе и ответе:

ЗначениеИсточникНазначение
Ваш X-Request-IdЗаголовок клиентского запросаНайти HTTP-попытку в своих журналах
client_request_idКлиентское значение, возвращённое в JSONСопоставить ответ с этой попыткой
request_idJSON-ответ сервераПередать поддержке серверный идентификатор
X-Request-Id ответаЗаголовок сервераСохранить трассировку при отсутствии тела
data.message_idУспешный ответ отправки, если поле возвращеноИдентифицировать сообщение, а не HTTP-запрос

Июньское обновление API представило эти поля. Здесь задача другая: не терять их ни в одной ветке обработки результата.

Рекомендуем назначать локальный ID бизнес-операции — например, одному одобренному ответу клиенту. Каждая HTTP-попытка получает отдельный локальный ID, а приложение хранит связь между ними. Это архитектура приложения, не дополнительные поля UnifyPort. Не включайте в идентификаторы имена клиентов, номера телефонов, текст сообщений или секреты.

Запишите один запрос без автоматического повтора

Начните с безопасного чтения текущего рабочего пространства. Пример для Node.js использует встроенный fetch и ключ из серверной переменной окружения UNIFYPORT_API_KEY. В журнал попадают только разрешённые диагностические поля, а не заголовки запроса или полное тело ответа.

Тайм-аут здесь — пример клиентской политики, не лимит сервиса. Функция выполняет одну попытку на уровне приложения и возвращает исходный Response вызывающему коду.

import { randomUUID } from 'node:crypto';

async function tracedWorkspaceRead(apiKey, operationId) {
  const clientAttemptId = randomUUID();
  const startedAt = new Date().toISOString();
  const startedMs = Date.now();
  let response;

  try {
    response = await fetch('https://api.unifyport.ai/v1/workspace', {
      headers: {
        'X-Api-Key': apiKey,
        'X-Request-Id': clientAttemptId
      },
      signal: AbortSignal.timeout(10000)
    });
  } catch {
    console.info({
      operation_id: operationId,
      client_attempt_id: clientAttemptId,
      started_at: startedAt,
      elapsed_ms: Date.now() - startedMs,
      outcome: 'no_http_response'
    });
    throw new Error('No HTTP response; inspect the local attempt record');
  }

  const headerId = response.headers.get('X-Request-Id');
  const body = response.status === 204
    ? null
    : await response.clone().json().catch(() => null);

  console.info({
    operation_id: operationId,
    client_attempt_id: clientAttemptId,
    started_at: startedAt,
    elapsed_ms: Date.now() - startedMs,
    http_status: response.status,
    server_request_id_header: headerId,
    server_request_id_body: body?.request_id ?? null,
    echoed_client_request_id: body?.client_request_id ?? null,
    error_code: body?.error?.code ?? null,
    numeric_code: body?.error?.numeric_code ?? null
  });
  return response;
}

const apiKey = process.env.UNIFYPORT_API_KEY;
if (!apiKey) throw new Error('Configure UNIFYPORT_API_KEY');
await tracedWorkspaceRead(apiKey, randomUUID());

Названия operation_id, outcome и подобные — локальные поля журнала, а не схема ответа API. Универсальная обработка включает 204, чтобы её можно было использовать для документированных операций без тела ответа. Само чтение рабочего пространства при успехе возвращает 200. Проверяйте пустой ответ локальным mock-сервером, а не удалением аккаунта ради теста логирования.

Сохранение значений и из заголовка, и из тела помогает заметить пропуски или расхождения. Ответ промежуточного сервиса может не соответствовать формату API. Сохраните HTTP-статус и локальную попытку, но не придумывайте серверный ID. В production ограничьте размер разбираемых данных и записей, права доступа к журналам и срок хранения.

Подготовьте полезный отчёт об ошибке

Справочник ошибок определяет error.code, error.numeric_code и error.message. Ветвите обработку по code или numeric_code, а не по тексту для человека. Числовой код может уточнить причину на стороне провайдера, не меняя прежний код или HTTP-статус.

НаблюдениеЧто сохранитьСледующее действие
JSON с успехом или ошибкойСтатус, серверный ID, клиентское значение, коды ошибокИнтерпретировать результат конкретной операции
204 без телаСтатус и заголовок ответа X-Request-IdНе считать отсутствие JSON сбоем API
Тело не разбирается или имеет неожиданный форматСтатус, ID из заголовка при наличии, локальный IDИсследовать путь ответа
Нет HTTP-ответаЛокальный ID, время начала, операцияСохранить результат как неизвестный до сверки

В обращении в поддержку укажите окружение, время попытки в UTC, HTTP-метод и шаблон маршрута, полученный серверный ID, локальный ID, статус, коды ошибок и краткое сравнение ожидаемого и фактического поведения. Идентификаторы аккаунта и диалога передавайте только по подходящему каналу с ограниченным доступом. Исключите API-ключи, сессионные данные, секрет подписи, токены ответа и URL медиа с подписью доступа.

Руководство по диагностике X Chat показывает, как такие записи помогают отличить проблему аккаунта от ошибки отдельного диалога. ID запроса указывает, где искать доказательства, но сам по себе не устанавливает причину.

Трассировка не разрешает повторную отправку

Для POST /v1/messages повторное использование клиентского ID не является документированной гарантией идемпотентности. При тайм-ауте отправка могла выполниться, хотя клиент не получил ответ. Сначала зафиксируйте неопределённость, затем решайте, допустима ли ещё одна отправка.

Это отличается от явного контракта ключей повтора LINE. LINE документирует x-line-accepted-request-id в ответе на повтор уже принятого запроса. Нельзя переносить это поведение на похожий по имени заголовок UnifyPort. Отдельный процесс описан в руководстве по retry key LINE.

Корреляция webhook — ещё один отдельный слой. Документация доставки определяет X-Device-Event-Id и X-Device-Delivery-Id; последний может использовать ID события как резервное значение и не гарантированно уникален для каждой HTTP-попытки. Не связывайте REST-запрос с событием только потому, что у обоих есть ID. Для уникальной записи каждой попытки приёма создавайте локальный идентификатор. Дедупликацию реализуйте отдельно по правилам типов событий, включая исключение HistorySync.

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

Почему после успешного удаления нет request_id?

Успешный 204 не содержит JSON. Читайте заголовок ответа X-Request-Id.

Можно ли узнать статус отправки по request_id?

Контракт трассировки не описывает endpoint проверки статуса запроса. Сохраните фактический результат и сверяйте неопределённые операции по поддерживаемым данным. Не конструируйте новый URL из ID.

Является ли client_request_id ключом идемпотентности?

Такая гарантия не документирована. Поле возвращает ваше значение для корреляции, а не предотвращает повторную отправку.

Следующий шаг и источники

Добавьте диагностическую сводку в один существующий API-клиент. Локальным mock-сервером проверьте JSON-ошибки, пустые ответы, неверный формат тела и сетевые сбои. Для ветвления используйте справочник ошибок.

Проверено 2026-10-01:

UnifyPort API

Превратите интеграцию сообщений в стабильный продуктовый pipeline.

Начните с отправки через единый API, затем возвращайте входящие сообщения в бизнес-систему стандартными событиями.