Эндпоинты
`webhooks.list`, `list_all`, `iterate`, `get`, `create`, `update`, `delete`, `rotate_secret`, `test`, `get_delivery` и `replay_delivery`, а также журналы доставок и активности.
Все методы
endpoint = client.webhooks.create( url: "https://acme.com/hooks/mail", eventTypes: ["email.sent", "email.bounced"], description: "Billing service") File.write(".openemail-webhook-secret", endpoint[:secret]) client.webhooks.listclient.webhooks.get(endpoint[:id])client.webhooks.update(endpoint[:id], enabled: false)client.webhooks.test(endpoint[:id])latest = client.webhooks.list_deliveries(endpoint[:id], limit: 1).items.firstclient.webhooks.get_delivery(endpoint[:id], latest[:id])client.webhooks.replay_delivery(endpoint[:id], latest[:id])rotated = client.webhooks.rotate_secret(endpoint[:id])File.write(".openemail-webhook-secret", rotated[:secret])client.webhooks.delete(endpoint[:id])create является ЕДИНСТВЕННЫМ случаем, когда возвращается секрет, не считая rotate_secret. Чтение никогда его не возвращает, поэтому сохраните его прежде всего остального. Не указывайте eventTypes, чтобы получить набор по умолчанию: все события email.*, кроме email.replied. email.replied, domain.*, suppression.*, file.* и form.* доходят до эндпоинта, только если он их называет.
У rotate_secret нет окна перекрытия. Старый секрет перестаёт работать немедленно, поэтому разверните новый до ротации. Вызов никогда не повторяется автоматически: повтор провёл бы ротацию второй раз и сделал бы недействительным секрет, который вернула первая попытка.
create тоже не повторяется, поэтому сетевой сбой может оставить созданный эндпоинт с секретом, которого вы так и не увидели. Проверьте list, прежде чем создавать его снова. По умолчанию рабочее пространство вмещает 10 эндпоинтов, а следующий сверх лимита даёт 422 workspace_limit_reached.
На что можно подписаться
OpenEmail::WEBHOOK_EVENTS является замороженным Hash всех имён событий, поэтому список можно отрисовать без запроса, а webhooks.list_events возвращает те же имена с фразой для каждого и пределы, которые действуют для эндпоинта. События являются событиями **почтового ящика**, а не этого API: email.received срабатывает для почты, пришедшей в приложение, а email.sent для сообщения, отправленного из композера. Подписка не равнозначна наблюдению за собственным API-трафиком.
file.uploaded срабатывает, когда файл помещают на страницу «Файлы», а file.deleted, когда его удаляют. Их data содержит fileId, filename, mimeType, sizeBytes, direction, to, threadId, messageId и uploadedAt или deletedAt. to является адресом, которому принадлежит файл, или nil для файла, принадлежащего всему рабочему пространству.
События файлов не входят в набор по умолчанию, поэтому эндпоинт получает их, только если называет их в eventTypes. Эндпоинт, ограниченный некоторыми адресами, узнаёт только о файлах этих адресов, поэтому загрузка для всего рабочего пространства, с to, равным nil, ему не отправляется.
form.submitted срабатывает, когда кто-то подписывается через одну из ваших форм, а form.confirmed, когда ожидающая подписка вступает в аудитории, потому что человек открыл ссылку подтверждения или потому что вы её одобрили. data у form.submitted содержит formId, formName, submissionId, email, status, answers, audienceIds, sourceUrl и submittedAt. data у form.confirmed содержит formId, formName, submissionId, email, audienceIds, via (равный link или approval) и confirmedAt.
Подписка через форму без двойного подтверждения отправляет form.submitted со status, равным added, и не отправляет form.confirmed, поэтому считайте эту пару моментом, когда человек вступает. Тот, кто подписывается снова до подтверждения, сохраняет тот же submissionId, а form.submitted приходит повторно, только если ответы изменились. Событий форм нет в наборе по умолчанию, а эндпоинт, ограниченный некоторыми адресами, никогда их не получает, потому что подписки принадлежат всему рабочему пространству.
Как убедиться, что это работает
result = client.webhooks.test("whe_3f9c2a7b1e4d8f60a5c7b92d")puts result.dig(:delivery, :status), result.dig(:delivery, :responseCode) client.webhooks.iterate_deliveries("whe_3f9c2a7b1e4d8f60a5c7b92d") do |delivery| puts "#{delivery[:eventType]} #{delivery[:status]} #{delivery[:responseCode]} #{delivery[:error]}"endtest отправляет подписанное синтетическое событие email.sent и ждёт завершения попытки. Он возвращается штатно, что бы ни ответил ваш приёмник, поэтому ветвитесь по delivery[:status], а не по тому, выбросил ли вызов исключение. 4xx является полезным ответом: URL доступен, а отказ пришёл от вашего собственного обработчика, часто от его проверки подписи.
responseCode, равный nil, означает, что ответа не было вовсе (DNS, TLS, таймаут), а это иной факт, чем ответ, сообщивший 0. Каждая строка несёт attempt и maxAttempts, поэтому несколько строк могут описывать одно событие: общий eventId обозначает событие, а номер попытки обозначает попытку. nextAttemptAt сообщает, когда назначен автоматический повтор после этой строки.
Повторная отправка
detail = client.webhooks.get_delivery("whe_3f9c2a7b1e4d8f60a5c7b92d", "whd_8c1e4a7f2b9d3e6a0c5f1b28")p detail[:payload], detail[:responseBody], detail[:replayRefusal] replay = client.webhooks.replay_delivery("whe_3f9c2a7b1e4d8f60a5c7b92d", "whd_8c1e4a7f2b9d3e6a0c5f1b28")p replay.dig(:delivery, :status), replay.dig(:delivery, :responseCode)Доставка, которая продолжает проваливаться, пробуется до 8 раз: сразу, затем через 1 минуту, 5 минут, 30 минут, 2 часа, 5 часов, 10 часов и 10 часов, всего около 27 с половиной часов. Повторяется только сбой, который стоит повторять: нет ответа, 408, 425, 429 или 5xx. Повторная отправка снова шлёт сохранённое событие с тем же id, type, createdAt и data, так что получатель, отбрасывающий уже обработанные идентификаторы, принимает его за уже известное событие. Новая только подпись.
replay_deliveryотправляет одно событие сейчас и возвращает ответ вашего сервера. Работает и с доставленной попыткой и никогда не повторяется. Перед отправкой ещё не начавшиеся автоматические повторы этого события приостанавливаются: если повторная отправка доставлена, они остаются отменёнными, а если она не удалась, возобновляются по своему расписанию.- Если в этот момент отправляется автоматический повтор того же события,
replay_deliveryничего не отправляет и выбрасывает 409retry_in_progress, а пока ещё отправляется другая повторная отправка этого события, выбрасывает 409replay_in_progress, поэтому ваш приёмник никогда не получит две копии одновременно, даже от двух повторных отправок, запущенных в один и тот же момент. Подождите несколько секунд и прочитайтеget_delivery, поскольку тот повтор или та повторная отправка могут доставить событие. Повторная отправка работает по одному событию: нет вызова, который заново отправляет все неудачные доставки. - Он также выбрасывает 409 для выключенного эндпоинта (
webhook_disabled), для события, которое эндпоинт больше не слушает (event_not_subscribed) или больше не охватывает (event_out_of_scope), и для попытки без сохранённого события (delivery_not_replayable).get_deliveryзаранее сообщает такой ответ какreplayRefusal.
Гем никогда сам не повторяет replay_delivery, потому что повтор после потерянного ответа отправил бы событие ещё раз.
Параметры: webhooks.create
urlStringобязательно- Куда отправляются доставки методом POST. Только HTTPS, и хост не может быть `localhost`, именем на `.localhost`, `.local` или `.internal` либо IP-литералом loopback, частной сети, CGNAT или link-local. Это серверный запрос на указанный вами адрес, поэтому такие значения дают 422 `invalid_webhook_url` для `url`. Проверка читает имя хоста в написанном виде, а каждая доставка заново разрешает хост и отказывается отправлять на адрес из любого из этих диапазонов. Доставки никогда не следуют перенаправлениям, поэтому регистрируйте конечный адрес. Сохраняется сериализация парсера URL от того, что вы отправили, поэтому `https://acme.com` читается обратно как `https://acme.com/`.
eventTypesArray<String>- Какие события доходят до этого эндпоинта: любые значения из `OpenEmail::WEBHOOK_EVENTS`. `create` ограничивает Array числом существующих событий, поэтому на одно больше даёт 422 для `eventTypes`, а `update` его не ограничивает. Ограничивается только длина, а повторяющееся имя сохраняется и читается обратно ровно так, как вы его отправили. Если параметр не указан или пуст, он сохраняется как пустой список, поэтому читается обратно как `["*"]`, и это означает все события `email.*`, кроме `email.replied` (сейчас их четырнадцать), и никогда семейства доменов, подавлений, файлов или форм. Семейство, добавленное позже, никогда не доходит до эндпоинта, который его не назвал, поэтому интеграция не может из-за нового выпуска начать получать данные в форме, которой никогда не видела.
descriptionString- Подпись эндпоинта, не более 200 символов, чтобы список вебхуков читался как имена, а не как столбец URL. Если не указана, сохраняется и возвращается как nil.
addressAllowlistArray<String>- Отдельные адреса, о которых узнаёт этот эндпоинт. Событие доставляется, когда адрес, к которому оно относится, есть в этом списке или когда его домен есть в `domainAllowlist`. Оставьте оба пустыми, и эндпоинт будет узнавать обо всех адресах рабочего пространства. Не более 50, а адрес, который не принадлежит этому рабочему пространству, даёт 422 `invalid_parameter`.
domainAllowlistArray<String>- Целые домены, о которых узнаёт этот эндпоинт, включая адреса, добавленные в них позже. Домен также несёт собственные события `domain.*`. Не более 25.
api_keyString- Создаёт эндпоинт с этим ключом вместо ключа клиента.
Ответ: созданный эндпоинт
Hash с ключами типа Symbol. get, list и update возвращают ту же форму без secret.
objectString- Всегда `webhook`, тот же дискриминатор, что возвращает обычное чтение, потому что секрет является одним дополнительным ключом в обычной форме, а не отдельным типом объекта. Присутствует ли `secret`, определяется вызванным методом, а не этим полем.
idString- Идентификатор эндпоинта: `whe_` и 24 шестнадцатеричных символа. Его принимает любой другой вызов для вебхуков: `get`, `update`, `delete`, `rotate_secret`, `test`, `list_deliveries`, `list_all_deliveries`, `iterate_deliveries`, `get_delivery` и `replay_delivery`.
urlString- Эндпоинт в сохранённом виде, прошедший проверки на HTTPS и запрещённые хосты. Это разобранный и заново сериализованный URL, поэтому сравнивайте с этим значением, а не со String, которую вы отправили.
descriptionString or nil- Подпись, которую вы дали, или nil, если не дали. `update`, отправляющий `description: nil`, её очищает.
eventTypesArray<String>- События подписки или `["*"]`, если эндпоинт не назвал ни одного. `["*"]` является тем, как пустой сохранённый список отображается при чтении, его нельзя отправить обратно, и он обозначает четырнадцать событий сообщений, а не весь каталог. `create` и `update` принимают только буквальные имена событий.
enabledBoolean- Выполняются ли попытки доставки. Отключённый эндпоинт пропускается при рассылке событий и сохраняет свой секрет и историю доставок. Здесь всегда true, поскольку `enabled` принимает только `update`.
disabledAtString or nil- Когда сервер отключил эндпоинт после 100 неудачных доставок подряд. nil, пока он включён, а также если вы отключили его сами.
disabledReasonString or nil- Почему сервер его отключил. nil всякий раз, когда `disabledAt` равно nil.
consecutiveFailuresInteger- Неудачные доставки подряд. Любое доставленное событие сбрасывает счётчик в 0, как и `update` с `enabled: true`.
addressAllowlistArray<String>- Отдельные адреса, о которых узнаёт этот эндпоинт.
domainAllowlistArray<String>- Целые домены, о которых узнаёт этот эндпоинт. Оба пустых списка означают все адреса рабочего пространства.
lastDeliveryAtString or nil- Метка времени ISO 8601 последней ПОПЫТКИ доставки, а не последнего успеха. Ставится и после неудачного POST, поэтому говорит о том, что к эндпоинту обращались, а `list_deliveries` говорит, чем это закончилось. nil до первой попытки, поэтому в `create` всегда nil.
createdAtString- Отметка времени ISO 8601 того, когда эндпоинт был зарегистрирован. `list` возвращает эндпоинты от новых к старым по этому полю.
secretString- Ключ HMAC-SHA-256, которым подписывается `X-OpenEmail-Signature` каждой доставки: `whsec_` и 43 символа base64url, и именно его вы передаёте в `OpenEmail.verify_webhook_signature` вместе с префиксом. Возвращается `create` и `rotate_secret` и больше ничем. Чтение никогда его не возвращает, поэтому сохраните его сейчас. Потерянный секрет можно только заменить через `rotate_secret`, который немедленно делает старый недействительным.
Фильтры журналов
failed = client.webhooks.list_workspace_deliveries(status: "failed", since: Time.now - 86_400)p failed.items.map { |delivery| [delivery[:endpointId], delivery[:eventType], delivery[:responseCode]] } history = client.webhooks.list_activity("whe_3f9c2a7b1e4d8f60a5c7b92d")p history.items.map { |change| [change[:type], change.dig(:actor, :label)] }list_deliveries читает один эндпоинт, а list_workspace_deliveries все эндпоинты или те, что называет endpoint_ids:, и оба принимают status:, since: и until:, фильтры вкладки «Доставки» в консоли. list_activity и list_workspace_activity читают журнал аудита: кто что создал, изменил, переключил, ротировал, протестировал, отправил повторно или удалил. У каждого есть версии list_all_ и iterate_, а каждая строка журнала рабочего пространства несёт endpointId. webhooks.stats возвращает числа, стоящие за вкладкой «Аналитика», за выбранное вами окно.
since: и until: принимают Time, DateTime или момент ISO 8601 в виде String, а Date из Ruby означает полночь UTC в этот день. until является ключевым словом Ruby, но работает как именованный аргумент, как любой другой: list_deliveries(id, since: start, until: finish).