Adal Outbound
Adal Outbound — отдельный контур для исходящей HTTP-доставки. Ваше приложение передаёт Adal адрес получателя, метод, заголовки и тело запроса, а Adal принимает сообщение в очередь, выполняет доставку, повторяет неудачные попытки и сохраняет их результаты.
Ваше приложение → Adal Outbound → URL получателя
Outbound работает независимо от входящего потока Adal. Для него не нужно создавать Server, Request или Destination: приложение работает с отдельным API и само указывает получателя каждого сообщения.
Beta: Adal Outbound развивается. Перед production-запуском проверьте интеграцию на тестовом получателе и предусмотрите обработку повторной доставки.
Когда использовать Outbound
Outbound подходит приложениям, которые отправляют вебхуки или другие HTTP-события во внешние системы и не хотят самостоятельно поддерживать очередь доставки, планировщик повторных попыток и журнал результатов.
Например:
SaaS-продукт отправляет события на webhook URL клиентов;
платформа уведомляет партнёров об изменениях заказов, платежей или других объектов;
внутренний сервис вызывает публичный API CRM, help desk или другой внешней системы;
команде нужна единая история исходящих доставок и данные для диагностики;
для разных потоков необходимо явно выбирать регион отправки и хранения истории.
Один запрос к Outbound создаёт одно сообщение для одного получателя.
Как работает Outbound
Рабочий поток состоит из пяти этапов:
Создайте refresh token в разделе Outbound → Tokens.
Обменяйте refresh token через Control Plane на access token.
Получите список регионов, выберите регион и сохраните его домен.
Отправляйте сообщения непосредственно в выбранный регион.
После истечения access token получите новый с помощью того же refresh token.
Outbound → Tokens
│
▼
Refresh token
│
▼
POST /auth/outbound
│
▼
Access token
│
├──► GET /outbound/servers
│
└──► POST https://{region-domain}/api/send
Список регионов не нужно запрашивать при каждом обновлении access token. Получите его при первоначальной настройке, сохраните выбранный domain и обновляйте список отдельно, когда хотите проверить появление новых регионов или изменить географию отправки.
Adal не выбирает регион автоматически и не выполняет межрегиональный failover. Регион определяет ваше приложение.
Аутентификация
Outbound использует два типа токенов.
| Токен | Для чего нужен | Где использовать |
|---|---|---|
| Refresh token | Получение access token | Только в Control Plane |
| Access token | Получение списка регионов и отправка сообщений | В Control Plane и выбранном регионе |
Refresh token
Refresh token — долгоживущий секрет приложения. Создайте его в разделе Outbound → Tokens в дашборде.
Значение refresh token показывается только при создании. Сохраните его сразу: позднее посмотреть или восстановить это значение нельзя. Если токен потерян, создайте новый.
Неиспользуемый или скомпрометированный refresh token можно отозвать в том же разделе. После отзыва получить с его помощью новый access token невозможно.
Храните refresh token в секретном хранилище приложения. Не помещайте его в исходный код, клиентское приложение, обычные логи или сообщения об ошибках.
Получение access token
Перед работой с Outbound API обменяйте refresh token на короткоживущий access token:
POST https://cp.adal.cloud/auth/outbound
Authorization: Bearer <refresh-token>
Успешный ответ содержит access token и сведения о сроке его действия:
{
"access_token": "<access-token>",
"expires_at": 1786200278343
}
Access token используется для получения списка регионов и отправки сообщений.
После истечения access token повторите запрос к /auth/outbound с тем же действующим refresh token. Повторно получать список регионов только из-за обновления access token не требуется.
Не передавайте refresh token региональным API и не используйте его для отправки сообщений.
Выбор региона
Получите список доступных регионов через Control Plane:
GET https://cp.adal.cloud/outbound/servers
Authorization: Bearer <access-token>
Пример элемента списка:
{
"key": "kz2",
"name": "Almaty, Kazakhstan",
"domain": "kz2.adal.cloud",
"country": "Kazakhstan",
"city": "Almaty"
}
| Поле | Описание |
|---|---|
key |
Идентификатор региона |
name |
Отображаемое название |
domain |
Домен регионального API |
country |
Страна размещения |
city |
Город размещения |
Для отправки используйте значение domain, возвращённое API. Не формируйте домен региона самостоятельно.
Например:
https://kz2.adal.cloud
Если выбранный регион недоступен, Adal не переключит сообщение на другой регион автоматически. Решение о смене региона и последствиях для размещения данных принимает ваше приложение.
Отправка сообщения
Отправьте сообщение непосредственно в выбранный регион:
POST https://kz2.adal.cloud/api/send
Authorization: Bearer <access-token>
Content-Type: application/json
Пример тела запроса:
{
"destination": "https://example.com/webhooks",
"method": "POST",
"headers": {
"Content-Type": ["application/json"],
"X-Event-Id": ["evt_01K2..."]
},
"body_base64": "eyJldmVudCI6Im9yZGVyLmNyZWF0ZWQifQ==",
"max_attempts": 3,
"add_idempotency": true
}
Поля сообщения
| Поле | Обязательно | Описание |
|---|---|---|
destination |
да | Публичный HTTP- или HTTPS-адрес получателя |
method |
да | HTTP-метод исходящего запроса |
headers |
нет | Заголовки исходящего запроса |
body_base64 |
нет | Тело исходящего запроса в Base64 |
max_attempts |
нет | Максимальное количество попыток доставки для сообщения |
add_idempotency |
нет | Нужно ли Adal генерировать Idempotency-Key, если он не передан; значение по умолчанию — true |
destination должен указывать на одного получателя. Если одно событие необходимо отправить нескольким системам, создайте отдельное сообщение для каждой из них.
Заголовки
Каждому имени заголовка соответствует массив значений:
{
"X-Event-Id": ["evt_01K2..."],
"X-Example": ["one", "two"]
}
Такой формат позволяет передавать несколько значений одного HTTP-заголовка.
Некоторыми транспортными заголовками управляет Adal. Не передавайте вручную Host, Content-Length, Connection, Transfer-Encoding и другие hop-by-hop или прокси-заголовки.
Если API отклоняет заголовок как зарезервированный, удалите его из headers.
Можно передать собственный стабильный идентификатор события — например, X-Event-Id или Idempotency-Key. Adal сохраняет пользовательские заголовки между повторными попытками.
Автоматический Idempotency-Key
По умолчанию Adal добавляет автоматически сгенерированный Idempotency-Key в исходящий HTTP-запрос, если пользователь не передал собственный ключ. Для управления этим поведением используйте опциональное поле add_idempotency:
| Поле | Тип | Обязательное | Значение по умолчанию |
|---|---|---|---|
add_idempotency |
boolean | нет | true |
Значением должен быть JSON boolean true или false. Строки и числа, например "true", 1 и 0, не допускаются.
add_idempotency |
Пользовательский Idempotency-Key |
Результат |
|---|---|---|
| не передан | отсутствует | Adal генерирует ключ в формате UUIDv7 |
| не передан | присутствует | Adal сохраняет пользовательский ключ |
true |
отсутствует | Adal генерирует ключ в формате UUIDv7 |
true |
присутствует | Adal сохраняет пользовательский ключ |
false |
отсутствует | Заголовок не добавляется |
false |
присутствует | Adal сохраняет пользовательский ключ |
Значение false отключает только автоматическую генерацию. Adal никогда не заменяет и не удаляет пользовательский Idempotency-Key.
Автоматически созданный ключ имеет формат UUIDv7:
Idempotency-Key: 01989d55-20df-7a72-87f9-6e50f3d78315
Adal создаёт ключ и сохраняет его вместе с сообщением до постановки доставки в очередь. Все попытки доставки одного сообщения, включая повторные, используют одно и то же значение. Новый самостоятельный вызов /send без пользовательского ключа получает новый UUIDv7.
Чтобы использовать собственный логический ключ, передайте его в headers:
{
"destination": "https://example.com/hook",
"method": "POST",
"headers": {
"Content-Type": ["application/json"],
"Idempotency-Key": ["order-create-12345"]
},
"body_base64": "eyJvcmRlcl9pZCI6MTIzNDV9",
"add_idempotency": true
}
В исходящий запрос попадёт неизменённый Idempotency-Key: order-create-12345. Это полезно, если вызывающему приложению нужно использовать один логический ключ в нескольких вызовах /send. Тот же пользовательский ключ сохраняется, даже если add_idempotency имеет значение false.
Чтобы отключить автоматическую генерацию при отсутствии ключа:
{
"destination": "https://example.com/hook",
"method": "POST",
"add_idempotency": false
}
В этом случае Adal не добавляет Idempotency-Key.
Тело запроса
body_base64 содержит байты тела исходящего HTTP-запроса, закодированные в Base64. Это позволяет передавать JSON, текстовые и бинарные данные в одном формате.
Например, тело:
{"event":"order.created"}
передаётся так:
{
"body_base64": "eyJldmVudCI6Im9yZGVyLmNyZWF0ZWQifQ=="
}
Base64 не задаёт тип содержимого. При необходимости передайте Content-Type отдельно в headers.
Ответ на отправку
После успешного приёма сообщения Adal возвращает:
202 Accepted
Ответ содержит идентификатор сообщения, выбранный регион и начальный статус pending. Например:
{
"id": 123,
"region": "kz2",
"status": "pending"
}
202 Accepted означает только то, что Adal принял сообщение и поставил его на доставку.
Это не означает, что:
получатель уже получил запрос;
получатель вернул успешный HTTP-ответ;
бизнес-операция внутри системы получателя завершилась успешно.
После принятия Adal выполняет доставку асинхронно и сохраняет результат каждой попытки.
Статусы сообщения
В истории Outbound используются следующие основные состояния:
| Статус | Значение |
|---|---|
pending |
Сообщение ожидает первой или следующей попытки |
delivering |
Adal выполняет HTTP-запрос к получателю |
success |
Получатель вернул HTTP-статус класса 2xx |
failed |
Разрешённые попытки исчерпаны без ответа 2xx |
Эти статусы относятся только к Outbound и не связаны со статусами доставки во входящем потоке Adal.
Доставка и повторные попытки
Доставка считается успешной, если получатель вернул HTTP-статус класса 2xx.
Ошибка соединения, DNS или TLS, тайм-аут либо любой другой HTTP-статус считаются неудачной попыткой. Пока лимит max_attempts не исчерпан, Adal планирует следующую попытку.
Задержка после неудачной попытки с номером N рассчитывается по формуле:
delay = N² minutes
Например:
| Неудачная попытка | Задержка до следующей попытки |
|---|---|
| 1 | 1 минута |
| 2 | 4 минуты |
| 3 | 9 минут |
Следующая попытка выполняется только тогда, когда заданный для сообщения лимит допускает ещё одну попытку.
Автоматические повторные попытки не расходуют дополнительные кредиты. Кредиты списываются один раз, когда Adal принимает сообщение к доставке.
Модель at least once и идемпотентность
Outbound использует модель доставки at least once. При некоторых сбоях один и тот же запрос может быть доставлен получателю больше одного раза.
Например, получатель мог обработать запрос, но его успешный ответ потерялся в сети. Adal не может надёжно отличить эту ситуацию от случая, когда запрос не был обработан, и выполнит повторную попытку.
Поэтому обработчик получателя должен:
быть идемпотентным там, где это возможно;
распознавать повторы по стабильному идентификатору события или idempotency key;
сохранять результат обработки этого идентификатора;
не создавать повторный платёж, заказ или уведомление при повторной доставке.
Adal сохраняет переданные заголовки и тело сообщения между попытками, поэтому идентификатор события остаётся тем же. Если Adal генерирует Idempotency-Key, одно и то же созданное значение также используется во всех попытках доставки сообщения. Это помогает получателю распознать повторную доставку, но для предотвращения повторного выполнения бизнес-операции получатель всё равно должен реализовать идемпотентную обработку или дедупликацию по ключу.
Ответ
2xxозначает, что получатель успешно ответил на HTTP-запрос. Он не доказывает, что бизнес-операция внутри системы получателя завершилась успешно.
Безопасность адреса получателя
Outbound выполняет запросы по URL, который передаёт пользователь, поэтому Adal проверяет destination и защищает инфраструктуру от SSRF.
Поддерживаются только публично доступные HTTP- и HTTPS-адреса. Отклоняются:
localhostи loopback-адреса;приватные и служебные диапазоны IP;
внутренние hostname;
URL со встроенным логином или паролем.
Адрес проверяется перед подключением и повторно при HTTP redirect. Поэтому Outbound нельзя использовать для прямой отправки на 127.0.0.1, private IP или внутренний hostname.
Примеры недопустимых адресов:
http://localhost/
http://127.0.0.1/
http://192.168.1.10/
Защита Adal не заменяет проверку входящих данных на стороне получателя. Получатель должен проверять подпись или другой способ аутентификации отправителя, валидировать содержимое и ограничивать полномочия используемых учётных данных.
История и диагностика
Текущее состояние сообщения и история попыток доступны в дашборде:
Outbound → Messages
Сначала выберите регион, через который было отправлено сообщение.
Для каждой попытки, в зависимости от результата, Adal показывает:
HTTP-код и заголовки ответа;
текст технической ошибки;
время DNS-разрешения;
время установления соединения и TLS;
TTFB и общее время запроса;
сведения о TLS-соединении;
цепочку HTTP redirects и итоговый URL.
Эти данные помогают отличить ошибку DNS от проблемы TLS, тайм-аута, медленного ответа приложения или неуспешного HTTP-статуса.
Тело HTTP-ответа получателя Adal не сохраняет и не показывает.
История сообщений и попыток доступна через дашборд, а не через публичный API.
Региональная обработка и хранение
Сообщение отправляется непосредственно через выбранный регион. Там же хранятся данные, необходимые для доставки и диагностики:
URL получателя;
HTTP-метод;
заголовки;
тело сообщения;
текущее состояние;
история попыток.
Control Plane используется для аутентификации и получения списка регионов, но не является централизованным хранилищем истории Outbound.
Выбор региона может быть важен для сетевого маршрута, географии обработки и внутренних требований организации к размещению данных.
Сам по себе выбор региона не подтверждает соответствие конкретному закону или отраслевому стандарту. Такая оценка зависит от состава данных, договоров и применимых требований.
Adal не выполняет автоматический failover между регионами. Если приложение переключается на другой регион, учитывайте, что история старых и новых сообщений будет находиться в разных регионах.
Кредиты
Кредиты списываются при успешном принятии сообщения Adal, а не при каждой попытке доставки.
Повторные попытки, которые Adal выполняет для уже принятого сообщения, не требуют дополнительных кредитов.
Автоматически созданный Idempotency-Key считается служебным заголовком. Он не учитывается в max_header_count, max_headers_bytes, при проверке максимального размера запроса и при расчёте количества списываемых кредитов. Поэтому автоматическая генерация не может увеличить стоимость сообщения или привести к превышению пользовательского лимита заголовков.
Если Idempotency-Key передал пользователь, он считается обычным пользовательским заголовком и учитывается в лимитах заголовков, при проверке размера запроса и при расчёте кредитов.
Если доступного баланса недостаточно, сообщение не принимается к доставке и ответ 202 Accepted не возвращается. Проверяйте ответ API до того, как считать сообщение поставленным в очередь.
Актуальные правила расчёта стоимости, баланс и ограничения плана смотрите в дашборде. Не фиксируйте эти значения в коде интеграции.
Ошибки и устранение неполадок
Считайте сообщение принятым только после ответа 202 Accepted. Любой другой ответ означает, что постановка в очередь не подтверждена.
Основные причины ошибок:
неверный, истёкший или отозванный токен;
истёкший access token;
ошибка формата запроса;
неподдерживаемый HTTP-метод;
запрещённый HTTP-заголовок;
недопустимый или заблокированный SSRF-защитой
destination;недостаточный баланс;
превышение rate limit.
| Проблема | Что проверить |
|---|---|
| Не удаётся получить access token | Refresh token существует и не был отозван |
| Access token истёк | Повторите /auth/outbound с действующим refresh token |
| Refresh token потерян или отозван | Создайте новый в Outbound → Tokens |
| Не удаётся отправить сообщение в регион | Используйте актуальный domain, полученный из списка регионов |
| API отклоняет заголовок | Удалите зарезервированный или hop-by-hop заголовок |
destination отклонён |
Убедитесь, что это публичный HTTP- или HTTPS-адрес без встроенных credentials |
Сообщение долго остаётся в pending |
Откройте историю попыток в нужном регионе |
Сообщение получило failed |
Изучите техническую ошибку, HTTP-статусы и timings каждой попытки |
| Сообщение не видно в дашборде | Выберите тот же регион, через который выполнялась отправка |
Не записывайте токены, полный URL получателя, заголовки или тело сообщения в обычные логи и системы аналитики: они могут содержать персональные данные и секреты.
Ограничения
В текущем потоке Outbound:
регион выбирает приложение, автоматического выбора и межрегионального failover нет;
одно сообщение предназначено для одного
destination;получатель должен быть публично доступен по HTTP или HTTPS;
тело HTTP-ответа получателя не сохраняется;
история сообщений и попыток доступна в дашборде, но не через публичный API.
Outbound не обеспечивает exactly-once delivery, строгий порядок доставки или успешное выполнение бизнес-операции у получателя.
Полный поток интеграции
1. Создайте refresh token в дашборде и получите с его помощью access token
POST https://cp.adal.cloud/auth/outbound
Authorization: Bearer <refresh-token>
Refresh token сохраните при создании в дашборде. Полученный access token используйте для API-запросов.
2. Получите список регионов
GET https://cp.adal.cloud/outbound/servers
Authorization: Bearer <access-token>
Выберите регион и сохраните возвращённый domain в конфигурации приложения.
3. Отправьте сообщение
POST https://kz2.adal.cloud/api/send
Authorization: Bearer <access-token>
Content-Type: application/json
{
"destination": "https://example.com/webhooks",
"method": "POST",
"headers": {
"Content-Type": ["application/json"],
"X-Event-Id": ["evt_01K2..."]
},
"body_base64": "eyJldmVudCI6Im9yZGVyLmNyZWF0ZWQifQ==",
"max_attempts": 3,
"add_idempotency": true
}
Считайте сообщение принятым только после ответа 202 Accepted. Сохраните его идентификатор для сопоставления с историей в дашборде.
4. Обновляйте access token
Когда access token истечёт, получите новый с помощью refresh token. Не запрашивайте список регионов повторно только из-за обновления токена.
5. Проверьте результат доставки
Откройте Outbound → Messages, выберите регион отправки и проверьте состояние сообщения и историю попыток.
Краткая схема API
| Method | Endpoint | Авторизация | Назначение |
|---|---|---|---|
POST |
https://cp.adal.cloud/auth/outbound |
Refresh token | Получить или обновить access token |
GET |
https://cp.adal.cloud/outbound/servers |
Access token | Получить список регионов |
POST |
https://{region-domain}/api/send |
Access token | Поставить сообщение на доставку |
Связанные страницы
Получатели — доставка входящих Requests в настроенные Destinations
Повторные попытки — повторные попытки во входящем потоке
Основные понятия — основные сущности и архитектура Adal
Хранение данных — правила хранения входящих Requests