Вебхуки и обратные вызовы
Что такое вебхуки?
В реальном мире 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
# Создание мока с вебхуком
# Put the mock (route, response, and its callback config) in a JSON file,
# then create it from that file:
mockarty-cli mock create -f payment-with-webhook.json
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
}'
Подписки на события
Колбэки выше срабатывают вокруг трафика моков и CRUD-операций над моками. Подписки на события идут дальше: они доставляют во внешнюю систему любое событие вашего пространства — прогоны тестов, раны тест-планов, активность по тест-кейсам, аномалии фаззинга, находки безопасности, хаос-эксперименты, нагрузочные тесты, контрактные проверки, задачи из трекера, сессии рекордера, ревью, срабатывания планировщика и здоровье раннеров.
Каждая подписка выбирает набор видов событий и способ доставки: HTTP (подписанный POST), Kafka (producer) или RabbitMQ (AMQP publish). Доставки повторяются с нарастающей задержкой, каждая попытка логируется, а неудачные попадают в очередь недоставленных (DLQ), откуда их можно перезапустить.
В интерфейсе: Настройки → Колбэки → Вебхуки — события выбираются сгруппированными чекбоксами (группу можно выбрать целиком одним кликом), затем указывается способ доставки.
Каталог событий
GET /api/v1/webhooks/catalog
Возвращает все виды событий, на которые можно подписаться, сгруппированные по областям:
{
"groups": [
{"id": "testing", "label": "Тестирование — планы, раны, тест-кейсы"},
{"id": "fuzzing", "label": "Фаззинг"},
{"id": "security", "label": "Безопасность"}
],
"kinds": [
{"kind": "test_plan.run.completed", "label": "Ран тест-плана завершён", "group": "testing", "description": "Ран тест-плана успешно завершился"},
{"kind": "fuzzing.finding.new", "label": "Новая аномалия фаззинга", "group": "fuzzing", "description": "Фаззер зафиксировал новую аномалию"},
{"kind": "security.finding.recorded", "label": "Находка безопасности", "group": "security", "description": "Движок безопасности зафиксировал новую находку"},
{"kind": "security.scan.completed", "label": "Сканирование безопасности завершено", "group": "security", "description": "Сканирование безопасности завершилось — в payload счётчики аномалий по severity"}
]
}
Группы покрывают: моки, тестирование (планы, раны, тест-кейсы), UI-тестирование, нагрузку и производительность, фаззинг, безопасность, хаос-инжиниринг, контрактное тестирование, задачи (трекер), рекордер, ревью, планировщик, раннеры и систему.
Управление подписками
# Список подписок
GET /api/v1/namespaces/{namespace}/webhooks/subscriptions
# Создать подписку
POST /api/v1/namespaces/{namespace}/webhooks/subscriptions
# Обновить / удалить
PATCH /api/v1/namespaces/{namespace}/webhooks/subscriptions/{id}
DELETE /api/v1/namespaces/{namespace}/webhooks/subscriptions/{id}
# Отправить тестовое событие через подписку
POST /api/v1/namespaces/{namespace}/webhooks/subscriptions/{id}/test
# Журнал доставки + повтор из DLQ
GET /api/v1/namespaces/{namespace}/webhooks/subscriptions/{id}/deliveries
POST /api/v1/namespaces/{namespace}/webhooks/subscriptions/deliveries/{id}/replay
Пример — отправлять события фаззинга и безопасности на свой эндпоинт
curl -X POST http://localhost:5770/api/v1/namespaces/production/webhooks/subscriptions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"name": "SOC intake",
"kinds": ["fuzzing.*", "security.*"],
"sinkKind": "http",
"sinkConfig": {"url": "https://soc.example.com/mockarty", "method": "POST"}
}'
kinds принимает точные имена и суффиксные шаблоны ("tcm.*", "test_plan.run.*").
Формат доставки (HTTP)
Каждое событие приходит одним JSON POST:
{
"id": "0c9f6c3e-…",
"kind": "fuzzing.finding.new",
"namespace": "production",
"subjectKind": "finding",
"subjectId": "f-123",
"createdAt": "2026-07-02T10:15:00Z",
"subscriptionName": "SOC intake",
"payload": {
"title": "Новая аномалия фаззинга",
"message": "Подозрение на SQL-инъекцию",
"url": "/ui/fuzzing?resultId=abc",
"severity": "warning",
"entity": {"id": "f-123", "kind": "finding", "name": "users-api fuzz"},
"data": { "…исходные поля события…": "" }
}
}
Заголовки: X-Mockarty-Event-Kind, X-Mockarty-Event-Id, X-Mockarty-Namespace, а при указании секрета в подписке (secretRef) — X-Mockarty-Signature: sha256=<HMAC-SHA256 от тела> для проверки подлинности на приёмнике.
Повторы: три попытки с нарастающей задержкой (1 с → 5 с → 30 с); после этого доставка попадает в очередь недоставленных и может быть перезапущена из журнала доставки.
Для AI-агентов
Та же функциональность доступна как MCP-инструменты: webhooks_catalog (каталог видов и групп событий), webhooks_create_subscription (создание HTTP/Kafka/RabbitMQ подписок) и webhooks_replay_delivery (повтор неудачной доставки).
Административные вебхуки
Административные вебхуки срабатывают при системных операциях. Только администраторы могут их настраивать.
Поддерживаемые операции
| Операция | Срабатывает когда |
|---|---|
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для фонового выполнения, чтобы ответ мока возвращался немедленно. -
Слишком большой
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, используемые в шаблонах обратных вызовов
- Рекордер – запись трафика и генерация моков с конфигурациями вебхуков