Webhooks & Callbacks
Mockarty can automatically fire HTTP requests, Kafka messages, or RabbitMQ messages whenever a mock is matched. This lets you simulate asynchronous notification systems, test webhook receivers, and chain multiple services together — all without writing any code.
Table of Contents
- Business Value
- Callback Types
- Trigger Modes
- HTTP Webhook Configuration
- Kafka Callback Configuration
- RabbitMQ Callback Configuration
- Template Variables in Callbacks
- Retry Policy
- Sync vs Async Execution
- Namespace Webhooks
- Admin Webhooks
- Examples
- Monitoring & Debugging
Business Value
Real-world APIs rarely operate in isolation. A payment gateway notifies your app via webhook. An order service publishes a Kafka event. A shipping service posts to RabbitMQ. Mockarty callbacks let you replicate these patterns in your test environment:
- Simulate async notifications — mock a Stripe payment endpoint that fires a
payment_intent.succeededwebhook to your app. - Test webhook receivers — verify your app handles retries, duplicate deliveries, and error payloads correctly.
- Chain services — mock A calls mock B via webhook, creating multi-step integration test flows without real infrastructure.
Callback Types
Each callback in the webhooks array has a type field:
| Type | Description | Default |
|---|---|---|
http |
HTTP request to an external URL | Yes (if type is omitted) |
kafka |
Publish a message to a Kafka topic | No |
rabbitmq |
Publish a message to a RabbitMQ exchange/queue | No |
Trigger Modes
The trigger field controls when a callback fires based on the mock’s response status code:
| Trigger | Fires When | Status Codes |
|---|---|---|
on_success |
Response is successful | 200–299 |
on_error |
Response is an error | 400+ |
always |
Every matched request | Any (default if omitted) |
{
"trigger": "on_success"
}
If trigger is omitted or empty, the callback fires on every matched request (always).
HTTP Webhook Configuration
HTTP is the default callback type. It sends an HTTP request to the specified URL after the mock responds.
Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
type |
string | No | "http" |
Callback type |
url |
string | Yes | — | Target URL |
method |
string | No | "POST" |
HTTP method: POST, PUT, PATCH, DELETE, GET |
headers |
object | No | {} |
Custom HTTP headers |
body |
any | No | null |
Request body (supports templating) |
timeout |
int | No | 30 |
Timeout in seconds |
retryCount |
int | No | 0 |
Number of retry attempts on failure |
retryDelay |
int | No | 1 |
Delay between retries in seconds |
async |
bool | No | true |
Run asynchronously (non-blocking) |
trigger |
string | No | "always" |
When to fire: on_success, on_error, always |
Minimal Example
curl -X POST http://localhost:5770/mock/create \
-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"
}
]
}'
Kafka Callback Configuration
Publish a message to a Kafka topic when a mock is matched.
Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
type |
string | Yes | — | Must be "kafka" |
kafkaBrokers |
string | Yes | — | Comma-separated broker addresses (e.g. "kafka1:9092,kafka2:9092") |
kafkaTopic |
string | Yes | — | Target topic name |
kafkaKey |
string | No | "" |
Message key (supports templating) |
kafkaUseSASL |
bool | No | false |
Enable SASL/PLAIN authentication |
kafkaUsername |
string | No | "" |
SASL username (required if kafkaUseSASL is true) |
kafkaPassword |
string | No | "" |
SASL password |
kafkaUseTLS |
bool | No | false |
Enable TLS (minimum TLS 1.2) |
headers |
object | No | {} |
Kafka message headers (key-value pairs) |
body |
any | No | null |
Message value (supports templating) |
timeout |
int | No | 30 |
Write timeout in seconds |
retryCount |
int | No | 0 |
Number of retry attempts |
retryDelay |
int | No | 1 |
Delay between retries in seconds |
trigger |
string | No | "always" |
When to fire |
Example
curl -X POST http://localhost:5770/mock/create \
-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 with 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"
}
Note: The
Content-Typeheader is automatically excluded from Kafka message headers. All other entries inheadersare passed as Kafka message headers.
RabbitMQ Callback Configuration
Publish a message to a RabbitMQ exchange or queue when a mock is matched.
Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
type |
string | Yes | — | Must be "rabbitmq" |
rabbitURL |
string | Yes | — | AMQP connection URI (e.g. "amqp://guest:guest@localhost:5672/") |
rabbitExchange |
string | No | "" |
Exchange name (empty = default exchange) |
rabbitRoutingKey |
string | No | "" |
Routing key |
rabbitQueue |
string | No | "" |
Queue name (used as routing key if exchange and routingKey are empty) |
rabbitMandatory |
bool | No | false |
Mandatory flag for publish |
headers |
object | No | {} |
AMQP message headers |
body |
any | No | null |
Message body (supports templating) |
timeout |
int | No | 30 |
Connection timeout in seconds |
retryCount |
int | No | 0 |
Number of retry attempts |
retryDelay |
int | No | 1 |
Delay between retries in seconds |
trigger |
string | No | "always" |
When to fire |
Example — Direct Queue
curl -X POST http://localhost:5770/mock/create \
-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"
}
]
}'
Example — Exchange with Routing Key
{
"type": "rabbitmq",
"rabbitURL": "amqp://guest:guest@localhost:5672/",
"rabbitExchange": "events",
"rabbitRoutingKey": "order.created",
"body": {
"orderId": "$.fake.UUID"
}
}
Note: If both
rabbitExchangeandrabbitRoutingKeyare empty butrabbitQueueis set, the queue name is used as the routing key with the default exchange. TheContent-Typeof the AMQP message defaults toapplication/jsonunless overridden inheaders.
Template Variables in Callbacks
Callback body and headers fields support the same templating engine as mock responses. You can use:
JsonPath — Extract from the Original Request (full guide)
{
"body": {
"userId": "$.req.userId",
"email": "$.req.email",
"authToken": "$.reqHeader.Authorization[0]"
}
}
Faker — Generate Dynamic Data (full reference)
{
"body": {
"webhookId": "$.fake.UUID",
"timestamp": "$.fake.UnixTime",
"ip": "$.fake.IPv4Address"
}
}
Store Values — Access Global/Chain/Mock Stores (documentation)
{
"body": {
"sessionId": "$.gS.currentSession",
"orderId": "$.cS.orderId",
"tempValue": "$.mS.computed"
}
}
String Body with Templates
You can use a string body instead of an object. If it contains $. expressions, they are processed:
{
"body": "Order $.req.orderId has been processed at $.fake.Date"
}
Response Data
Access the mock’s own response fields:
{
"body": {
"mockResponseStatus": "$.resp.status",
"statusCode": "$.statusCode"
}
}
Retry Policy
All callback types support automatic retries on failure.
| Field | Default | Description |
|---|---|---|
retryCount |
0 |
Number of additional attempts after the first failure (0 = no retries) |
retryDelay |
1 |
Delay between attempts in seconds |
Retries use a fixed delay (not exponential backoff). If the context is cancelled during a retry delay, the callback stops immediately.
{
"url": "http://unreliable-service.example.com/webhook",
"retryCount": 3,
"retryDelay": 2,
"timeout": 10
}
This attempts up to 4 total requests (1 initial + 3 retries), waiting 2 seconds between each attempt, with a 10-second timeout per request.
Sync vs Async Execution
| Mode | async |
Behavior |
|---|---|---|
| Async | true (default) |
Callback runs in a background goroutine. The mock response is returned immediately. |
| Sync | false |
Callback runs before the mock response is returned to the caller. Useful when the caller needs to know the callback completed. |
{
"url": "http://my-app.com/webhook",
"async": false,
"trigger": "on_success"
}
Warning: Sync callbacks with high
timeoutorretryCountvalues will delay the mock response to the client.
Namespace Webhooks
Namespace webhooks fire when CRUD operations occur on mocks within a namespace. These are configured separately from per-mock callbacks.
Supported Operations
| Operation | Fires When |
|---|---|
mock.create |
A mock is created in the namespace |
mock.update |
A mock is updated |
mock.delete |
A mock is deleted |
mock.restore |
A mock is restored from soft-delete |
user.add_to_namespace |
A user is added to the namespace |
API Endpoints
# List namespace webhooks
GET /ui/api/namespaces/{namespace}/webhooks
# Create/update a namespace webhook
POST /ui/api/namespaces/{namespace}/webhooks
# Delete a namespace webhook
DELETE /ui/api/namespaces/{namespace}/webhooks/{id}
Example — Notify Slack on Mock Changes
curl -X POST http://localhost:5770/ui/api/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
}'
Admin Webhooks
Admin webhooks fire on system-level operations. Only administrators can configure these.
Supported Operations
| Operation | Fires When |
|---|---|
namespace.create |
A new namespace is created |
namespace.delete |
A namespace is deleted |
namespace.update |
A namespace is updated |
namespace.user.add |
A user is added to a namespace |
user.create |
A new user is created |
user.delete |
A user is deleted |
user.update |
A user is updated |
user.password.set |
A user’s password is changed |
job.run |
A background job is triggered |
backup.create |
A backup is created |
backup.restore |
A backup is restored |
license.create |
A license is created |
license.update |
A license is updated |
license.delete |
A license is deleted |
API Endpoints
# List admin webhooks
GET /ui/api/admin/webhooks
# Create/update an admin webhook
POST /ui/api/admin/webhooks
# Delete an admin webhook
DELETE /ui/api/admin/webhooks/{id}
Example — Audit Log on User Creation
curl -X POST http://localhost:5770/ui/api/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
}'
Examples
1. Simulate Stripe Webhook on Payment Mock
When your app calls POST /api/payments, Mockarty responds with a success payload and fires a webhook to your app’s Stripe listener:
curl -X POST http://localhost:5770/mock/create \
-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. Forward Kafka Message on Order Creation
Mock an order API that publishes an event to Kafka for downstream consumers:
curl -X POST http://localhost:5770/mock/create \
-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. Chain: Mock -> Webhook -> Another Mock
Create a two-step flow where a payment mock triggers a notification mock:
Step 1 — Create the notification receiver mock:
curl -X POST http://localhost:5770/mock/create \
-H "Content-Type: application/json" \
-d '{
"id": "notification-receiver",
"http": {
"route": "/internal/notifications",
"httpMethod": "POST"
},
"response": {
"statusCode": 200,
"payload": {"notified": true}
},
"extractors": [
{
"from": "body",
"jsonPath": "$.orderId",
"toStore": "global",
"key": "lastNotifiedOrder"
}
]
}'
Step 2 — Create the payment mock that chains to it:
curl -X POST http://localhost:5770/mock/create \
-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/internal/notifications",
"method": "POST",
"body": {
"orderId": "$.req.orderId",
"event": "payment.completed",
"amount": "$.req.amount"
},
"async": false,
"trigger": "on_success"
}
]
}'
Now calling POST /api/pay will respond with success and call POST /internal/notifications on the same Mockarty instance, storing the order ID in the global store.
4. Error Webhook — Notify on Failure
Fire a callback only when the mock returns an error:
curl -X POST http://localhost:5770/mock/create \
-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. Multiple Callbacks on One Mock
A single mock can have multiple callbacks of different types:
{
"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"
}
]
}
Monitoring & Debugging
Application Logs
All callback activity is logged via structured logging (Zap). Look for these log messages:
| Log Level | Message | Meaning |
|---|---|---|
INFO |
Callback executed successfully |
Callback completed without error |
WARN |
Callback attempt failed |
A single attempt failed (may retry) |
ERROR |
Callback failed after all retries |
All attempts exhausted |
ERROR |
Failed to prepare webhook body |
Template rendering failed |
ERROR |
Panic in webhook executeCallback |
Unexpected panic (includes stack trace) |
Log entries include the callback type (http/kafka/rabbitmq) and the attempt number.
Tips
-
Use sync mode for debugging — Set
"async": falseto ensure callbacks complete before the response. This makes the timing predictable in logs. -
Start with retryCount: 0 — Add retries only after confirming the callback target is reachable.
-
Test the target first — Before creating the mock, verify the webhook URL / Kafka broker / RabbitMQ connection is accessible from the Mockarty host:
# Test HTTP target curl -v http://my-app:3000/webhooks/stripe # Test Kafka connectivity kafkacat -b kafka:9092 -L # Test RabbitMQ connectivity curl -u guest:guest http://rabbitmq:15672/api/overview -
Check callback body templating — If your callback body uses
$.req.*expressions, make sure the original request contains the expected fields. Unresolved templates are sent as literal strings. -
Timeout tuning — The default 30-second timeout is generous. For local services, 5-10 seconds is usually enough. For external services behind firewalls, increase as needed.
-
Context cancellation — If the client disconnects before an async callback completes, the callback may be cancelled during retry delays. For critical callbacks, keep
retryDelayshort.
See Also
- API Reference — Full REST API documentation including webhook endpoints
- Faker Reference — All available Faker functions for dynamic callback payloads
- JsonPath Guide — JsonPath expressions for extracting request data in callbacks
- Stores — Global, Chain, and Mock store systems used in callback templates
- Recorder — Record live traffic and generate mocks with webhook configurations