Вебхуки и обратные вызовы
Что такое вебхуки?
В реальном мире API не всегда отвечают мгновенно. Когда вы совершаете платёж, платёжная система сразу возвращает «принято», но фактический результат (успех или неудача) приходит позже через вебхук – HTTP-запрос от сервера к ВАШЕМУ приложению.
Аналогия из жизни: Заказ доставки еды. Приложение сразу подтверждает «Заказ оформлен!» (ответ API). Но через 30 минут курьер звонит в дверь (вебхук), сообщая, что еда доставлена. Обратные вызовы Mockarty позволяют имитировать обе части этого процесса.
Mockarty может автоматически отправлять HTTP-запросы, сообщения в Kafka или RabbitMQ при каждом срабатывании мока. Это позволяет имитировать асинхронные системы уведомлений, тестировать приёмники вебхуков и выстраивать цепочки взаимодействия между сервисами — без написания кода.
Совет: Примеры кода доступны для cURL, CLI и SDK-клиентов (Go, Python, Java). Смотрите Руководство по SDK для установки и настройки. Смотрите Руководство по CLI для командной утилиты.
URL-адреса в примерах: Во всех примерах используется
localhost:5770как адрес Mockarty по умолчанию. Если ваш экземпляр работает на удалённом сервере, заменитеlocalhost:5770на реальный адрес (например,https://mockarty.company.comилиhttp://192.168.1.50:5770). Подробнее — в разделе Полезные функции и советы.
Содержание
- Практическая ценность
- Типы обратных вызовов
- Режимы срабатывания
- Конфигурация HTTP-вебхука
- Конфигурация обратного вызова Kafka
- Конфигурация обратного вызова RabbitMQ
- Шаблонные переменные в обратных вызовах
- Политика повторных попыток
- Синхронное и асинхронное выполнение
- Вебхуки пространства имён
- Административные вебхуки
- Примеры
- Мониторинг и отладка
Практическая ценность
Реальные API редко работают изолированно. Платёжный шлюз уведомляет ваше приложение через вебхук. Сервис заказов публикует событие в Kafka. Сервис доставки отправляет сообщение в RabbitMQ. Обратные вызовы Mockarty позволяют воспроизвести эти паттерны в тестовом окружении:
- Имитация асинхронных уведомлений — мок платёжного эндпоинта Stripe отправляет вебхук
payment_intent.succeededв ваше приложение. - Тестирование приёмников вебхуков — проверка корректной обработки повторных доставок, дублей и ошибочных данных.
- Цепочки сервисов — мок A вызывает мок B через вебхук, создавая многошаговые интеграционные тестовые сценарии без реальной инфраструктуры.
Типы обратных вызовов
Каждый обратный вызов в массиве webhooks имеет поле type:
| Тип | Описание | По умолчанию |
|---|---|---|
http |
HTTP-запрос на внешний URL | Да (если type не указан) |
kafka |
Публикация сообщения в топик Kafka | Нет |
rabbitmq |
Публикация сообщения в exchange/очередь RabbitMQ | Нет |
Режимы срабатывания
Поле trigger определяет, когда обратный вызов срабатывает, в зависимости от кода ответа мока:
| Триггер | Срабатывает когда | Коды ответа |
|---|---|---|
on_success |
Ответ успешный | 200–299 |
on_error |
Ответ содержит ошибку | 400+ |
always |
При каждом совпавшем запросе | Любой (по умолчанию, если не указан) |
{
"trigger": "on_success"
}
Если trigger не указан или пуст, обратный вызов срабатывает при каждом совпавшем запросе (always).
Конфигурация HTTP-вебхука
HTTP — тип обратного вызова по умолчанию. Он отправляет HTTP-запрос на указанный URL после ответа мока.
Поля
| Поле | Тип | Обязательное | По умолчанию | Описание |
|---|---|---|---|---|
type |
string | Нет | "http" |
Тип обратного вызова |
url |
string | Да | — | Целевой URL |
method |
string | Нет | "POST" |
HTTP-метод: POST, PUT, PATCH, DELETE, GET |
headers |
object | Нет | {} |
Пользовательские HTTP-заголовки |
body |
any | Нет | null |
Тело запроса (поддерживает шаблоны) |
timeout |
int | Нет | 30 |
Таймаут в секундах |
retryCount |
int | Нет | 0 |
Количество повторных попыток при ошибке |
retryDelay |
int | Нет | 1 |
Задержка между повторными попытками в секундах |
async |
bool | Нет | false |
Асинхронное выполнение (неблокирующее) |
trigger |
string | Нет | "always" |
Когда срабатывать: on_success, on_error, always |
Минимальный пример
cURL
curl -X POST http://localhost:5770/api/v1/mocks \
-H "Content-Type: application/json" \
-d '{
"id": "payment-with-webhook",
"http": {
"route": "/api/payments",
"httpMethod": "POST"
},
"response": {
"statusCode": 200,
"payload": {
"id": "$.fake.UUID",
"status": "succeeded"
}
},
"webhooks": [
{
"url": "http://my-app:3000/webhooks/stripe",
"method": "POST",
"headers": {
"X-Stripe-Signature": "t=123456,v1=abc123"
},
"body": {
"type": "payment_intent.succeeded",
"data": {
"amount": 2500,
"currency": "usd"
}
},
"trigger": "on_success"
}
]
}'
CLI
# Создание мока с вебхуком
mockarty-cli mock create --id payment-with-webhook \
--route /api/payments --method POST \
--status 200 \
--payload '{"id":"$.fake.UUID","status":"succeeded"}' \
--webhook-url http://my-app:3000/webhooks/stripe \
--webhook-trigger on_success \
--webhook-body '{"type":"payment_intent.succeeded","data":{"amount":2500,"currency":"usd"}}'
Go
// Создание мока с вебхуком
err := client.Mocks().Create(ctx, &mockarty.Mock{
ID: "payment-with-webhook",
HTTP: &mockarty.HTTPConfig{
Route: "/api/payments",
HTTPMethod: "POST",
},
Response: &mockarty.Response{
StatusCode: 200,
Payload: map[string]any{
"id": "$.fake.UUID", "status": "succeeded",
},
},
Webhooks: []mockarty.Webhook{{
URL: "http://my-app:3000/webhooks/stripe",
Method: "POST",
Headers: map[string]string{"X-Stripe-Signature": "t=123456,v1=abc123"},
Body: map[string]any{
"type": "payment_intent.succeeded",
"data": map[string]any{"amount": 2500, "currency": "usd"},
},
Trigger: "on_success",
}},
})
Python
# Создание мока с вебхуком
client.mocks.create({
"id": "payment-with-webhook",
"http": {"route": "/api/payments", "httpMethod": "POST"},
"response": {
"statusCode": 200,
"payload": {"id": "$.fake.UUID", "status": "succeeded"},
},
"webhooks": [{
"url": "http://my-app:3000/webhooks/stripe",
"method": "POST",
"headers": {"X-Stripe-Signature": "t=123456,v1=abc123"},
"body": {"type": "payment_intent.succeeded", "data": {"amount": 2500, "currency": "usd"}},
"trigger": "on_success",
}],
})
Java
// Создание мока с вебхуком
client.mocks().create(Mock.builder()
.id("payment-with-webhook")
.http(HttpConfig.builder().route("/api/payments").httpMethod("POST").build())
.response(Response.builder().statusCode(200)
.payload(Map.of("id", "$.fake.UUID", "status", "succeeded")).build())
.webhooks(List.of(Webhook.builder()
.url("http://my-app:3000/webhooks/stripe")
.method("POST")
.trigger("on_success")
.body(Map.of("type", "payment_intent.succeeded",
"data", Map.of("amount", 2500, "currency", "usd")))
.build()))
.build());
Конфигурация обратного вызова Kafka
Публикация сообщения в топик Kafka при срабатывании мока.
Поля
| Поле | Тип | Обязательное | По умолчанию | Описание |
|---|---|---|---|---|
type |
string | Да | — | Должен быть "kafka" |
kafkaBrokers |
string | Да | — | Адреса брокеров через запятую (например, "kafka1:9092,kafka2:9092") |
kafkaTopic |
string | Да | — | Имя целевого топика |
kafkaKey |
string | Нет | "" |
Ключ сообщения (поддерживает шаблоны) |
kafkaUseSASL |
bool | Нет | false |
Включить аутентификацию SASL/PLAIN |
kafkaUsername |
string | Нет | "" |
Имя пользователя SASL (обязательно, если kafkaUseSASL = true) |
kafkaPassword |
string | Нет | "" |
Пароль SASL |
kafkaUseTLS |
bool | Нет | false |
Включить TLS (минимум TLS 1.2) |
headers |
object | Нет | {} |
Заголовки сообщения Kafka (пары ключ-значение) |
body |
any | Нет | null |
Значение сообщения (поддерживает шаблоны) |
timeout |
int | Нет | 30 |
Таймаут записи в секундах |
retryCount |
int | Нет | 0 |
Количество повторных попыток |
retryDelay |
int | Нет | 1 |
Задержка между повторными попытками в секундах |
trigger |
string | Нет | "always" |
Когда срабатывать |
Пример
curl -X POST http://localhost:5770/api/v1/mocks \
-H "Content-Type: application/json" \
-d '{
"id": "order-created-kafka",
"http": {
"route": "/api/orders",
"httpMethod": "POST"
},
"response": {
"statusCode": 201,
"payload": {
"orderId": "$.fake.UUID",
"status": "created"
}
},
"webhooks": [
{
"type": "kafka",
"kafkaBrokers": "localhost:9092",
"kafkaTopic": "orders.created",
"kafkaKey": "$.req.customerId",
"body": {
"event": "order.created",
"orderId": "$.fake.UUID",
"timestamp": "$.fake.UnixTime"
},
"trigger": "on_success"
}
]
}'
Kafka с SASL/TLS
{
"type": "kafka",
"kafkaBrokers": "kafka-prod.example.com:9093",
"kafkaTopic": "events",
"kafkaUseSASL": true,
"kafkaUsername": "my-service",
"kafkaPassword": "secret",
"kafkaUseTLS": true,
"body": {"event": "test"},
"trigger": "always"
}
Примечание: Заголовок
Content-Typeавтоматически исключается из заголовков сообщения Kafka. Все остальные записи вheadersпередаются как заголовки сообщения Kafka.
Конфигурация обратного вызова RabbitMQ
Публикация сообщения в exchange или очередь RabbitMQ при срабатывании мока.
Поля
| Поле | Тип | Обязательное | По умолчанию | Описание |
|---|---|---|---|---|
type |
string | Да | — | Должен быть "rabbitmq" |
rabbitURL |
string | Да | — | URI подключения AMQP (например, "amqp://guest:guest@localhost:5672/") |
rabbitExchange |
string | Нет | "" |
Имя exchange (пусто = exchange по умолчанию) |
rabbitRoutingKey |
string | Нет | "" |
Ключ маршрутизации |
rabbitQueue |
string | Нет | "" |
Имя очереди (используется как ключ маршрутизации, если exchange и routingKey пусты) |
rabbitMandatory |
bool | Нет | false |
Флаг mandatory для публикации |
headers |
object | Нет | {} |
Заголовки AMQP-сообщения |
body |
any | Нет | null |
Тело сообщения (поддерживает шаблоны) |
timeout |
int | Нет | 30 |
Таймаут подключения в секундах |
retryCount |
int | Нет | 0 |
Количество повторных попыток |
retryDelay |
int | Нет | 1 |
Задержка между повторными попытками в секундах |
trigger |
string | Нет | "always" |
Когда срабатывать |
Пример — прямая очередь
curl -X POST http://localhost:5770/api/v1/mocks \
-H "Content-Type: application/json" \
-d '{
"id": "notification-rabbitmq",
"http": {
"route": "/api/notifications/send",
"httpMethod": "POST"
},
"response": {
"statusCode": 200,
"payload": {"status": "queued"}
},
"webhooks": [
{
"type": "rabbitmq",
"rabbitURL": "amqp://guest:guest@localhost:5672/",
"rabbitQueue": "notifications",
"body": {
"type": "email",
"to": "$.req.email",
"subject": "Your order has been placed"
},
"trigger": "on_success"
}
]
}'
Пример — exchange с ключом маршрутизации
{
"type": "rabbitmq",
"rabbitURL": "amqp://guest:guest@localhost:5672/",
"rabbitExchange": "events",
"rabbitRoutingKey": "order.created",
"body": {
"orderId": "$.fake.UUID"
}
}
Примечание: Если
rabbitExchangeиrabbitRoutingKeyпусты, но заданrabbitQueue, имя очереди используется как ключ маршрутизации с exchange по умолчанию.Content-TypeAMQP-сообщения по умолчаниюapplication/json, если не переопределён вheaders.
Шаблонные переменные в обратных вызовах
Поля body и headers обратных вызовов поддерживают тот же шаблонизатор, что и ответы моков. Доступные возможности:
JsonPath — извлечение данных из исходного запроса (полное руководство)
{
"body": {
"userId": "$.req.userId",
"email": "$.req.email",
"authToken": "$.reqHeader.Authorization[0]"
}
}
Faker — генерация динамических данных (полный справочник)
{
"body": {
"webhookId": "$.fake.UUID",
"timestamp": "$.fake.UnixTime",
"ip": "$.fake.IPv4"
}
}
Значения из хранилищ — доступ к Global/Chain/Mock Store (документация)
{
"body": {
"sessionId": "$.gS.currentSession",
"orderId": "$.cS.orderId",
"tempValue": "$.mS.computed"
}
}
Строковое тело с шаблонами
Вместо объекта можно использовать строковое тело. Если оно содержит выражения $., они будут обработаны:
{
"body": "Order $.req.orderId has been processed at $.fake.Date"
}
Данные ответа
Доступ к полям собственного ответа мока:
{
"body": {
"mockResponseStatus": "$.response.status"
}
}
Примечание:
$.responseссылается на объект payload ответа мока. Например, если ваш мок возвращает{"status": "ok", "data": {"id": 1}}, то$.response.statusразрешится в"ok", а$.response.data.id— в1.
Политика повторных попыток
Все типы обратных вызовов поддерживают автоматические повторные попытки при ошибке.
| Поле | По умолчанию | Описание |
|---|---|---|
retryCount |
0 |
Количество дополнительных попыток после первой ошибки (0 = без повторов) |
retryDelay |
1 |
Задержка между попытками в секундах |
Повторные попытки используют фиксированную задержку (не экспоненциальный откат). Если контекст отменяется во время задержки повторной попытки, обратный вызов немедленно прекращается.
{
"url": "http://unreliable-service.example.com/webhook",
"retryCount": 3,
"retryDelay": 2,
"timeout": 10
}
В этом примере выполняется до 4 запросов (1 начальный + 3 повтора) с задержкой 2 секунды между попытками и таймаутом 10 секунд на каждый запрос.
Синхронное и асинхронное выполнение
| Режим | async |
Поведение |
|---|---|---|
| Асинхронный | true (по умолчанию) |
Обратный вызов выполняется в фоновой горутине. Ответ мока возвращается немедленно. |
| Синхронный | false |
Обратный вызов выполняется до возврата ответа мока вызывающей стороне. Полезно, когда вызывающей стороне важно знать, что обратный вызов завершён. |
{
"url": "http://my-app.com/webhook",
"async": true,
"trigger": "on_success"
}
Внимание: Синхронные обратные вызовы (
"async": false) с большими значениямиtimeoutилиretryCountзадерживают ответ мока клиенту.
Сетевая безопасность и защита от SSRF
По умолчанию Mockarty блокирует исходящие webhook-вызовы к private, loopback, link-local и cloud-metadata IP-адресам. Это защита от SSRF — без неё вредоносная конфигурация мока могла бы заставить сервер Mockarty сканировать собственную внутреннюю сеть.
Блокируемые диапазоны (по умолчанию):
127.0.0.0/8(loopback)10.0.0.0/8,172.16.0.0/12,192.168.0.0/16(private IPv4)169.254.0.0/16(link-local / cloud metadata, например169.254.169.254)- IPv6-эквиваленты (
::1,fc00::/7,fe80::/10)
Переменные окружения (задаются на resolver; а также на административной ноде, если она сама обслуживает моки):
| Переменная | По умолчанию | Описание |
|---|---|---|
ALLOW_PROXY_TO_PRIVATE_IPS |
false |
Когда false, защита от SSRF активна и webhook-вызовы к перечисленным диапазонам отклоняются. Устанавливайте true ТОЛЬКО в изолированных dev/CI-окружениях, где целевые сервисы легитимно находятся на private IP |
WEBHOOK_POOL_SIZE |
10 |
Максимальное число одновременных исходящих webhook-воркеров на resolver / на административную ноду. Это ограничение пропускной способности на уровне ноды, а не на уровне мока |
Правило для продакшена. В продакшене оставляйте
ALLOW_PROXY_TO_PRIVATE_IPS=false. Если нужно вызвать внутренний сервис, опубликуйте его через reverse proxy с публичным DNS-именем или явный allow-list, а не снимайте SSRF-защиту глобально.
О примере Stripe-Signature ниже. Пример в разделе «Примеры» использует литеральную строку
v1=test_signature. Mockarty НЕ вычисляет реальные HMAC-подписи — если вам нужна валидная подпись, сгенерируйте её в клиентском коде до вызова мока и передайте через тело запроса или через значение в Store. Шаблонизация выполняет только подстановку строк.
Вебхуки пространства имён
Вебхуки пространства имён срабатывают при CRUD-операциях с моками внутри пространства имён. Они настраиваются отдельно от обратных вызовов конкретных моков.
Поддерживаемые операции
| Операция | Срабатывает когда |
|---|---|
mock.create |
Мок создан в пространстве имён |
mock.update |
Мок обновлён |
mock.delete |
Мок удалён |
mock.restore |
Мок восстановлен из мягкого удаления |
user.add_to_namespace |
Пользователь добавлен в пространство имён |
API-эндпоинты
# Список вебхуков пространства имён
GET /api/v1/namespaces/{namespace}/webhooks
# Создание/обновление вебхука пространства имён
POST /api/v1/namespaces/{namespace}/webhooks
# Удаление вебхука пространства имён
DELETE /api/v1/namespaces/{namespace}/webhooks/{id}
Пример — уведомление в Slack при изменении моков
curl -X POST http://localhost:5770/api/v1/namespaces/production/webhooks \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"operation": "mock.create",
"url": "https://hooks.slack.com/services/T.../B.../xxx",
"method": "POST",
"body": {
"text": "New mock created in production namespace"
},
"enabled": true,
"retryCount": 2,
"retryDelay": 3
}'
Административные вебхуки
Административные вебхуки срабатывают при системных операциях. Только администраторы могут их настраивать.
Поддерживаемые операции
| Операция | Срабатывает когда |
|---|---|
namespace.create |
Создано новое пространство имён |
namespace.delete |
Пространство имён удалено |
namespace.update |
Пространство имён обновлено |
namespace.user.add |
Пользователь добавлен в пространство имён |
user.create |
Создан новый пользователь |
user.delete |
Пользователь удалён |
user.update |
Пользователь обновлён |
user.password.set |
Пароль пользователя изменён |
job.run |
Запущена фоновая задача |
backup.create |
Создана резервная копия |
backup.restore |
Восстановлена резервная копия |
license.create |
Создана лицензия |
license.update |
Лицензия обновлена |
license.delete |
Лицензия удалена |
API-эндпоинты
# Список административных вебхуков
GET /api/v1/admin/webhooks
# Создание/обновление административного вебхука
POST /api/v1/admin/webhooks
# Удаление административного вебхука
DELETE /api/v1/admin/webhooks/{id}
Пример — аудит-лог при создании пользователя
curl -X POST http://localhost:5770/api/v1/admin/webhooks \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <admin-token>" \
-d '{
"operation": "user.create",
"url": "https://audit.internal.com/events",
"method": "POST",
"body": {
"event": "user.created",
"source": "mockarty"
},
"enabled": true
}'
Примеры
1. Имитация вебхука Stripe при платёжном моке
Когда ваше приложение вызывает POST /api/payments, Mockarty возвращает успешный ответ и отправляет вебхук на Stripe-слушатель вашего приложения:
curl -X POST http://localhost:5770/api/v1/mocks \
-H "Content-Type: application/json" \
-d '{
"id": "stripe-payment",
"http": {
"route": "/api/payments/intents",
"httpMethod": "POST"
},
"response": {
"statusCode": 200,
"payload": {
"id": "pi_$.fake.UUID",
"status": "succeeded",
"amount": "$.req.amount"
}
},
"webhooks": [
{
"url": "http://my-app:3000/webhooks/stripe",
"method": "POST",
"headers": {
"Stripe-Signature": "t=$.fake.UnixTime,v1=test_signature",
"Content-Type": "application/json"
},
"body": {
"id": "evt_$.fake.UUID",
"type": "payment_intent.succeeded",
"data": {
"object": {
"id": "pi_$.fake.UUID",
"amount": "$.req.amount",
"currency": "$.req.currency",
"status": "succeeded"
}
}
},
"trigger": "on_success",
"retryCount": 3,
"retryDelay": 5,
"timeout": 15
}
]
}'
2. Отправка сообщения в Kafka при создании заказа
Мок API заказов, который публикует событие в Kafka для нижестоящих потребителей:
curl -X POST http://localhost:5770/api/v1/mocks \
-H "Content-Type: application/json" \
-d '{
"id": "order-with-kafka-event",
"http": {
"route": "/api/orders",
"httpMethod": "POST"
},
"response": {
"statusCode": 201,
"payload": {
"orderId": "$.fake.UUID",
"status": "created"
}
},
"webhooks": [
{
"type": "kafka",
"kafkaBrokers": "kafka:9092",
"kafkaTopic": "orders.events",
"kafkaKey": "$.req.customerId",
"headers": {
"event-type": "order.created",
"source": "order-service"
},
"body": {
"orderId": "$.fake.UUID",
"customerId": "$.req.customerId",
"items": "$.req.items",
"total": "$.req.total",
"createdAt": "$.fake.Date"
},
"trigger": "on_success"
}
]
}'
3. Цепочка: мок -> вебхук -> другой мок
Двухшаговый сценарий, где платёжный мок запускает мок уведомлений:
Шаг 1 — создание мока-приёмника уведомлений:
curl -X POST http://localhost:5770/api/v1/mocks \
-H "Content-Type: application/json" \
-d '{
"id": "notification-receiver",
"http": {
"route": "/internal/notifications",
"httpMethod": "POST"
},
"response": {
"statusCode": 200,
"payload": {"notified": true}
},
"extract": {
"gStore": {
"lastNotifiedOrder": "$.req.orderId"
}
}
}'
Шаг 2 — создание платёжного мока с цепочкой:
curl -X POST http://localhost:5770/api/v1/mocks \
-H "Content-Type: application/json" \
-d '{
"id": "payment-with-chain",
"http": {
"route": "/api/pay",
"httpMethod": "POST"
},
"response": {
"statusCode": 200,
"payload": {
"paid": true,
"orderId": "$.req.orderId"
}
},
"webhooks": [
{
"url": "http://localhost:5770/stubs/sandbox/internal/notifications",
"method": "POST",
"body": {
"orderId": "$.req.orderId",
"event": "payment.completed",
"amount": "$.req.amount"
},
"async": false,
"trigger": "on_success"
}
]
}'
Теперь при вызове POST /api/pay будет возвращён успешный ответ и выполнен вызов POST /stubs/sandbox/internal/notifications на том же экземпляре Mockarty с сохранением ID заказа в глобальное хранилище.
4. Вебхук при ошибке — уведомление о сбое
Обратный вызов срабатывает только при возврате ошибки моком:
curl -X POST http://localhost:5770/api/v1/mocks \
-H "Content-Type: application/json" \
-d '{
"id": "flaky-service",
"http": {
"route": "/api/fragile",
"httpMethod": "GET"
},
"response": {
"statusCode": 503,
"payload": {"error": "service unavailable"}
},
"webhooks": [
{
"url": "http://alerting-system:8080/alerts",
"body": {
"alert": "fragile-service-mock returned error",
"statusCode": 503
},
"trigger": "on_error"
}
]
}'
5. Несколько обратных вызовов на одном моке
Один мок может иметь несколько обратных вызовов разных типов:
{
"webhooks": [
{
"type": "http",
"url": "http://audit.internal/log",
"body": {"event": "order.created"},
"trigger": "always"
},
{
"type": "kafka",
"kafkaBrokers": "kafka:9092",
"kafkaTopic": "orders.events",
"body": {"event": "order.created"},
"trigger": "on_success"
},
{
"type": "rabbitmq",
"rabbitURL": "amqp://guest:guest@rabbitmq:5672/",
"rabbitQueue": "notifications",
"body": {"notify": true},
"trigger": "on_success"
}
]
}
Мониторинг и отладка
Логи приложения
Вся активность обратных вызовов логируется через структурированное логирование (Zap). Ключевые сообщения в логах:
| Уровень | Сообщение | Значение |
|---|---|---|
INFO |
Callback executed successfully |
Обратный вызов завершён без ошибок |
WARN |
Callback attempt failed |
Одна попытка завершилась ошибкой (возможен повтор) |
ERROR |
Callback failed after all retries |
Все попытки исчерпаны |
ERROR |
Failed to prepare webhook body |
Ошибка рендеринга шаблона |
ERROR |
Panic in webhook executeCallback |
Непредвиденная паника (включает стек-трейс) |
Записи в логах содержат type обратного вызова (http/kafka/rabbitmq) и номер попытки.
Рекомендации
-
Используйте синхронный режим для отладки — установите
"async": false, чтобы обратные вызовы завершались до отправки ответа. Это делает порядок событий в логах предсказуемым. -
Начинайте с retryCount: 0 — добавляйте повторные попытки только после подтверждения доступности целевого сервиса.
-
Сначала проверьте доступность — перед созданием мока убедитесь, что URL вебхука / брокер Kafka / подключение RabbitMQ доступны с хоста Mockarty:
# Проверка HTTP-цели curl -v http://my-app:3000/webhooks/stripe # Проверка подключения к Kafka kafkacat -b kafka:9092 -L # Проверка подключения к RabbitMQ curl -u guest:guest http://rabbitmq:15672/api/overview -
Проверяйте шаблоны в теле обратного вызова — если тело использует выражения
$.req.*, убедитесь, что исходный запрос содержит ожидаемые поля. Необработанные шаблоны отправляются как обычные строки. -
Настройка таймаутов — таймаут по умолчанию 30 секунд достаточно щедрый. Для локальных сервисов обычно достаточно 5–10 секунд. Для внешних сервисов за файрволами увеличивайте по необходимости.
-
Отмена контекста — если клиент отключается до завершения асинхронного обратного вызова, обратный вызов может быть отменён во время задержки повторной попытки. Для критичных обратных вызовов используйте короткое значение
retryDelay.
Частые ошибки
Наиболее распространённые проблемы при настройке обратных вызовов:
-
Использование
localhostв URL вебхука, когда Mockarty в Docker. Внутри Dockerlocalhostуказывает на сам контейнер. Используйте имя Docker-сервиса (например,http://my-app:3000/webhook) илиhttp://host.docker.internal:3000/webhookдля доступа к хост-машине. -
Не учли, что синхронный режим задерживает ответ. При
"async": falseответ мока не возвращается вызывающей стороне до завершения обратного вызова. Если цель обратного вызова медленная или недоступная, ваш тест зависнет. По умолчанию"async": true(фоновое выполнение), поэтому устанавливайтеfalseтолько когда обратный вызов должен завершиться до отправки ответа. -
Слишком большой
retryCountбез учёта таймаута. ПриretryCount: 5иtimeout: 30неудачный обратный вызов будет блокировать до 150 секунд (5 x 30с). Начните сretryCount: 1иtimeout: 5для локальных сервисов. -
Ожидание, что
$.req.*ссылается на тело вебхука. Выражения$.req.*ссылаются на исходный запрос, который вызвал мок, а не на запрос обратного вызова. Если вам нужны данные из ответа обратного вызова, это пока не поддерживается. -
Путаница между вебхуками мока и пространства имён. Вебхуки уровня мока (в массиве
webhooksмока) срабатывают при совпадении конкретного мока. Вебхуки пространства имён срабатывают при создании/обновлении/удалении моков через API. Они служат разным целям. -
Kafka: неправильный адрес брокера. Поле
kafkaBrokersдолжно содержать адрес, видимый с хоста Mockarty. В Docker Compose используйте имя сервиса Kafka (например,kafka:9092), а неlocalhost:9092.
Смотрите также
- Справочник API – полная документация REST API, включая эндпоинты вебхуков
- Справочник Faker – все доступные функции Faker для динамических данных в обратных вызовах
- Руководство по JsonPath – выражения JsonPath для извлечения данных запроса в обратных вызовах
- Хранилища – системы хранилищ Global, Chain и Mock, используемые в шаблонах обратных вызовов
- Рекордер – запись трафика и генерация моков с конфигурациями вебхуков