Docs Webhooks & Callbacks

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

  1. Business Value
  2. Callback Types
  3. Trigger Modes
  4. HTTP Webhook Configuration
  5. Kafka Callback Configuration
  6. RabbitMQ Callback Configuration
  7. Template Variables in Callbacks
  8. Retry Policy
  9. Sync vs Async Execution
  10. Namespace Webhooks
  11. Admin Webhooks
  12. Examples
  13. 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.succeeded webhook 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-Type header is automatically excluded from Kafka message headers. All other entries in headers are 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 rabbitExchange and rabbitRoutingKey are empty but rabbitQueue is set, the queue name is used as the routing key with the default exchange. The Content-Type of the AMQP message defaults to application/json unless overridden in headers.


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 timeout or retryCount values 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

  1. Use sync mode for debugging — Set "async": false to ensure callbacks complete before the response. This makes the timing predictable in logs.

  2. Start with retryCount: 0 — Add retries only after confirming the callback target is reachable.

  3. 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
    
  4. 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.

  5. 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.

  6. Context cancellation — If the client disconnects before an async callback completes, the callback may be cancelled during retry delays. For critical callbacks, keep retryDelay short.


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