Creating Mocks
Callbacks
Mocks cover the requests your system sends to its integrations. Many integrations also call you back: a payment provider confirms a charge, a job runner posts its result. Callbacks are how Mocko simulates that direction. A mock schedules one, and Mocko delivers an HTTP request to your service after it responds, with any delay you want.
Your first callback
A callback is defined in its own block, separate from mocks, and triggered by name with the callback helper. This pair simulates a payment provider: the mock answers PENDING right away, and two seconds later Mocko posts the approval to your service, like the real provider would.
callback "payment-approved" {
url = "http://localhost:3000/webhooks/payments"
delay = 2000
body = <<-EOF
{ "id": "{{payload.id}}", "status": "APPROVED" }
EOF
}
mock "POST /payments" {
format = "json"
body = <<-EOF
{{callback 'payment-approved' (object id=request.body.id)}}
{ "id": "{{request.body.id}}", "status": "PENDING" }
EOF
}Two seconds after that response, your service on port 3000 receives:
POST /webhooks/payments
content-type: application/json
{ "id": "pay-42", "status": "APPROVED" }The flow that used to block your team in staging, waiting for a webhook that never comes from a sandbox, now completes on its own.
The callback block
callback "payment-approved" {
name = "Payment approved" # optional label for the UI
method = "POST" # optional, default POST
host = "backend" # a host block slug...
path = "/payments/{{payload.id}}" # ...plus a path, or:
# url = "http://localhost:3000/cb" # an absolute URL
delay = 2000 # default delay in ms, default 0
headers {
X-Source = "mocko"
}
body = <<-EOF
{ "id": "{{payload.id}}" }
EOF
}The target is either host plus path, or an absolute url, never both. Targeting a host block is the better default in shared deployments: the callback follows the host's destination, so pointing an environment at a different backend is one host edit instead of a hunt through callback URLs.
path, url, body, and header values are all Bigodon templates. When a body is set and no header says otherwise, Content-Type defaults to application/json.
Triggering from mocks
{{callback 'payment-approved'}} {{! block defaults }}
{{callback 'payment-approved' delay=5000}} {{! override the delay }}
{{callback 'payment-approved' (object id=request.body.id)}} {{! object payload }}
{{callback 'payment-approved' 'EXPIRED'}} {{! any JSON value works }}The positional argument is the payload, any JSON-serializable value. It travels with the scheduled callback and comes back as payload in the callback's templates. The delay= parameter overrides the block's delay; without either, delivery happens right after the response.
The helper renders as an empty string and never slows the mock down: the callback is enqueued when the mock responds, and the delay counts from there. Triggering an unknown slug, or a host without a destination, fails the mock with a 500 so broken wiring surfaces immediately instead of silently dropping deliveries.
Rendered at delivery time
Callback templates render when the callback fires, not when it is scheduled. Their context is payload and data; the triggering request is gone by then, so anything you need from it goes into the payload. Rendering late is what makes callbacks the right place for state transitions: pair them with flagsand the state flips when the "webhook" happens, not when the request came in.
callback "payment-approved" {
host = "backend"
path = "/webhooks/payments"
delay = 2000
body = <<-EOF
{{setFlag (append 'payments:' payload.id ':status') 'APPROVED'}}
{ "id": "{{payload.id}}", "status": "APPROVED" }
EOF
}
mock "GET /payments/{id}" {
format = "json"
body = <<-EOF
{{= $status (default (getFlag (append 'payments:' request.params.id ':status')) 'PENDING')}}
{ "id": "{{request.params.id}}", "status": "{{$status}}" }
EOF
}Polling GET /payments/pay-42 answers PENDING until the callback fires, then APPROVED: the two consumption styles real payment APIs offer, webhooks and polling, from one pair of blocks.
setStatus, setHeader, proxy, and callback itself. There is no response to shape and no chaining; a mock schedules every callback it needs up front, each with its own delay.Pending callbacks in the UI
The control panel's Callbacks tab lists every scheduled delivery with a live countdown, which mock triggered it, and its payload. From there you can fire one immediately instead of waiting out a delay, cancel it, or clear everything. Definitions get a Fire button too, with a payload editor, so you can push a webhook to your service without involving a mock at all. The tab also creates and edits callbacks, the same way it manages UI mocks; file-defined callbacks show as read only.
From tests and scripts
Everything the UI does goes through an HTTP API on the mock server, and the SDK wraps it:
const pending = await mocko.fireCallback('payment-approved', { id: 'pay-42' });
await mocko.fireCallback('payment-approved', { id: 'pay-43' }, { delay: 60_000 });
await mocko.listPendingCallbacks();
await mocko.firePendingCallback(pending.id); // skip the delay
await mocko.cancelPendingCallback(pending.id);
await mocko.clearPendingCallbacks();In tests, never sleep through a delay: schedule the callback, assert it is pending, then fire it. Manual and SDK fires are immediate by default; the block's delay only applies to mock-triggered callbacks.
Behavior details
- Because rendering happens at delivery, editing a callback's body, or repointing its host, affects deliveries already scheduled. If the definition is deleted while pending, the delivery is dropped with a warning in the server log.
- Delivery failures (connection errors, non-2xx responses) are logged with the callback slug and dropped. There are no automatic retries.
- In storeless mode, pending callbacks live in memory and are lost on restart. With Redis they survive restarts and are delivered exactly once across replicas, so long-delayed callbacks, even days out, are safe.
- Definitions merge like hosts: a UI-created callback with the same slug as a file-defined one takes precedence.
mocko validatechecks callback blocks: target shape, template syntax, and hosts that do not exist.
Next
One piece of the mock file format remains: shared fixtures. Continue to Data Blocks.