Webhooks & Callbacks
What Are Webhooks?
In the real world, APIs do not always respond immediately. When you make a payment, the payment processor returns “accepted” right away, but the actual result (success or failure) arrives later via a webhook – an HTTP request from the server to YOUR application.
Real-world analogy: Ordering food delivery. The app immediately confirms “Order placed!” (the API response). But 30 minutes later, the driver rings your doorbell (the webhook) to tell you the food has arrived. Mockarty callbacks let you simulate both parts of this flow.
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.
Tip: Code examples are available for cURL, CLI, and SDK clients (Go, Python, Java). See the SDK Guide for installation and setup. See the CLI User Guide for the command-line tool.
About URLs in examples: All examples use
localhost:5770as the default Mockarty address. If your instance runs on a remote server, replacelocalhost:5770with its actual address (e.g.https://mockarty.company.comorhttp://192.168.1.50:5770). See Tips & Useful Features for details.
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 | false |
Run asynchronously (non-blocking) |
trigger |
string | No | "always" |
When to fire: on_success, on_error, always |
Minimal Example
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 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/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 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/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"
}
]
}'
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.IPv4"
}
}
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": "$.response.status"
}
}
Note:
$.responserefers to the mock’s response payload object. For example, if your mock returns{"status": "ok", "data": {"id": 1}}, then$.response.statusresolves to"ok"and$.response.data.idresolves to1.
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": true,
"trigger": "on_success"
}
Warning: Sync callbacks (
"async": false) with hightimeoutorretryCountvalues will delay the mock response to the client.
Network Security & SSRF Guard
By default Mockarty blocks outbound webhook calls to private, loopback, link-local, and cloud-metadata IP addresses. This is an SSRF guard — without it, a malicious mock configuration could make the Mockarty server probe its own internal network.
Blocked ranges (default):
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 endpoints like169.254.169.254)- IPv6 equivalents (
::1,fc00::/7,fe80::/10)
Relevant environment variables (set on the resolver, and on the admin node if it serves mocks directly):
| Variable | Default | Description |
|---|---|---|
ALLOW_PROXY_TO_PRIVATE_IPS |
false |
When false, the SSRF guard is active and webhook calls to the ranges above are rejected. Set to true ONLY in isolated dev/CI environments where targets legitimately live on private IPs |
WEBHOOK_POOL_SIZE |
10 |
Maximum number of concurrent outbound webhook workers per resolver / admin node. This is a node-level throughput cap, not a per-mock limit |
Production rule. Leave
ALLOW_PROXY_TO_PRIVATE_IPS=falsein production. If you need to call an internal service, put it behind a reverse proxy with a public hostname or an explicit allow-list, rather than opening the SSRF guard globally.
About the Stripe-Signature example below. The example in the “Examples” section uses a hardcoded literal string
v1=test_signature. Mockarty does NOT compute real HMAC signatures — if you need a valid signature, generate it in your client code before calling the mock and pass it through the request body or via a Store value. Templating is string substitution only.
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 /api/v1/namespaces/{namespace}/webhooks
# Create/update a namespace webhook
POST /api/v1/namespaces/{namespace}/webhooks
# Delete a namespace webhook
DELETE /api/v1/namespaces/{namespace}/webhooks/{id}
Example — Notify Slack on Mock Changes
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
}'
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 /api/v1/admin/webhooks
# Create/update an admin webhook
POST /api/v1/admin/webhooks
# Delete an admin webhook
DELETE /api/v1/admin/webhooks/{id}
Example — Audit Log on User Creation
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
}'
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/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. 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/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. 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/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"
}
}
}'
Step 2 — Create the payment mock that chains to it:
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"
}
]
}'
Now calling POST /api/pay will respond with success and call POST /stubs/sandbox/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/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. 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.
Common Mistakes
Here are the most frequent issues when setting up callbacks:
-
Using
localhostin the webhook URL when Mockarty is in Docker. Inside Docker,localhostpoints to the container itself. Use the Docker service name (e.g.,http://my-app:3000/webhook) orhttp://host.docker.internal:3000/webhookto reach the host machine. -
Not realizing sync mode delays the response. With
"async": false, the mock response is not returned to the caller until the callback completes. If the callback target is slow or unreachable, your test will hang. The default is"async": true(background execution), so only setfalsewhen you need the callback to complete before the response. -
Setting
retryCounttoo high without thinking about timeout. WithretryCount: 5andtimeout: 30, a failing callback will block for up to 150 seconds (5 x 30s). Start withretryCount: 1andtimeout: 5for local services. -
Expecting
$.req.*to resolve from the webhook body. The$.req.*expressions refer to the original request that triggered the mock, not the callback request. If you need data from the callback response, it is not currently supported. -
Mixing up mock-level and namespace-level webhooks. Mock-level webhooks (in the
webhooksarray of a mock) fire when that specific mock is matched. Namespace webhooks fire when mocks are created/updated/deleted via the API. They serve different purposes. -
Kafka: not specifying the broker address correctly. The
kafkaBrokersfield must be the address as seen from the Mockarty host. In Docker Compose, use the Kafka service name (e.g.,kafka:9092), notlocalhost:9092.
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
- Notification Channels – System-level notification channels (Telegram, Slack, Discord, etc.)
- Chaos Testing – Inject failures and latency alongside webhook callbacks