Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

HTTP & webhooks

Telephony rarely lives alone — there’s usually a backend that records calls or drives them. ringo-flow can both call an HTTP API mid-scenario and mock one your system under test calls back.

Call an API

http(method, url) makes a request and returns a response you can assert on:

#![allow(unused)]
fn main() {
let res = http("GET", env("API_URL") + "/calls/last");
res.expect_status(200);
assert(res.json("from")).equals("+49301234567");
}

res.json("a.b.0.c") walks a dotted JSON path; res.status / res.body / res.header(name) are there too. For requests with headers or a body, pass an options map — see HTTP:

#![allow(unused)]
fn main() {
http("POST", env("API_URL") + "/calls", #{
    headers: #{ "Content-Type": "application/json" },
    body: #{ to: "+49301234567" },
});
}

Mock a webhook (webhook-driven call control)

Some telephony APIs call your webhook for a call and expect you to answer with the actions to perform. Stand up a built-in mock server, point the API at it, and assert on what it received.

mock_server() starts the server; on(...) answers a route dynamically, json_response builds the body, and last_request / request_count inspect what arrived:

#![allow(unused)]
fn main() {
let hooks = mock_server();

// Answer the webhook with the call actions to perform.
hooks.on("POST", "/voice", |req| {
    if req.json("event") == "incoming_call" {
        json_response(#{ actions: [ #{ type: "answer" } ] })
    } else {
        json_response(#{ actions: [ #{ type: "hangup" } ] })
    }
});

// Tell the system under test where to send its webhooks.
http("PUT", env("API_URL") + "/config?webhook=" + hooks.url + "/voice");

a.dial(env("API_NUMBER"));

// Wait for the webhook the same way you wait for anything else.
await_until(|| assert(hooks.request_count("/voice")).equals(1), "10s");

let req = hooks.last_request("/voice");
assert(req.json("event")).equals("incoming_call");
}

Notes:

  • The on(...) responder runs on a worker thread, so keep it pure (request → response): no agent verbs inside it.
  • Routes match by exact path or regex("/calls/.*"), and by a method or any ("*" / omit the method). Re-register a route with respond(...) to stage the next answer between webhooks.
  • The server is stopped automatically at the end of the scenario.

See the HTTP mock server and Mock request reference for everything.