Echo
Reflects your request back as JSON. Works on every hostname — useful for verifying client behaviour before pointing at the chaos endpoints.
Reflects the incoming request as JSON: method, URL, path, query parameters, all headers, and body. Accepts any HTTP method. If the body is valid JSON it is parsed and included as a nested object; otherwise it is included as a raw string.
expect: 200 OK with Content-Type: application/json. Body is a JSON object reflecting the request that arrived: method, url, path, query, headers, and body.
# GET with query params
curl -i 'https://catastrophic.io/echo?foo=bar&baz=1'
# POST with a JSON body
curl -i -X POST \
-H 'Content-Type: application/json' \
-H 'X-My-Header: hello' \
-d '{"key":"value"}' \
https://catastrophic.io/echo
import json, urllib.request
# GET
resp = urllib.request.urlopen("https://catastrophic.io/echo?foo=bar&baz=1")
print(json.loads(resp.read()))
# POST with a body
data = json.dumps({"key": "value"}).encode()
req = urllib.request.Request(
"https://catastrophic.io/echo",
data=data,
headers={"Content-Type": "application/json", "X-My-Header": "hello"},
)
print(json.loads(urllib.request.urlopen(req).read()))
// GET
const r1 = await fetch("https://catastrophic.io/echo?foo=bar&baz=1");
console.log(await r1.json());
// POST
const r2 = await fetch("https://catastrophic.io/echo", {
method: "POST",
headers: { "Content-Type": "application/json", "X-My-Header": "hello" },
body: JSON.stringify({ key: "value" }),
});
console.log(await r2.json());
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
// GET
r1, _ := http.Get("https://catastrophic.io/echo?foo=bar&baz=1")
b1, _ := io.ReadAll(r1.Body)
fmt.Println(string(b1))
r1.Body.Close()
// POST
body := strings.NewReader(`{"key":"value"}`)
req, _ := http.NewRequest("POST", "https://catastrophic.io/echo", body)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-My-Header", "hello")
r2, _ := http.DefaultClient.Do(req)
b2, _ := io.ReadAll(r2.Body)
fmt.Println(string(b2))
r2.Body.Close()
}
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
// serde_json = "1"
fn main() -> Result<(), Box> {
let client = reqwest::blocking::Client::new();
// GET
let r1 = client.get("https://catastrophic.io/echo?foo=bar&baz=1").send()?;
println!("{}", r1.text()?);
// POST
let r2 = client
.post("https://catastrophic.io/echo")
.header("X-My-Header", "hello")
.json(&serde_json::json!({ "key": "value" }))
.send()?;
println!("{}", r2.text()?);
Ok(())
}
// Java 11+ HttpClient
import java.net.URI;
import java.net.http.*;
public class Echo {
public static void main(String[] args) throws Exception {
var client = HttpClient.newHttpClient();
// GET
var r1 = client.send(
HttpRequest.newBuilder(URI.create("https://catastrophic.io/echo?foo=bar&baz=1")).build(),
HttpResponse.BodyHandlers.ofString());
System.out.println(r1.body());
// POST
var r2 = client.send(
HttpRequest.newBuilder(URI.create("https://catastrophic.io/echo"))
.header("Content-Type", "application/json")
.header("X-My-Header", "hello")
.POST(HttpRequest.BodyPublishers.ofString("{\"key\":\"value\"}"))
.build(),
HttpResponse.BodyHandlers.ofString());
System.out.println(r2.body());
}
}
// .NET 6+
using System.Text;
using var client = new HttpClient();
// GET
Console.WriteLine(await client.GetStringAsync("https://catastrophic.io/echo?foo=bar&baz=1"));
// POST — use HttpRequestMessage to attach arbitrary headers
var req = new HttpRequestMessage(HttpMethod.Post, "https://catastrophic.io/echo");
req.Headers.Add("X-My-Header", "hello");
req.Content = new StringContent("{\"key\":\"value\"}", Encoding.UTF8, "application/json");
var r2 = await client.SendAsync(req);
Console.WriteLine(await r2.Content.ReadAsStringAsync());
require "net/http"
require "json"
# GET
r1 = Net::HTTP.get_response(URI("https://catastrophic.io/echo?foo=bar&baz=1"))
puts r1.body
# POST
uri = URI("https://catastrophic.io/echo")
req = Net::HTTP::Post.new(uri)
req["Content-Type"] = "application/json"
req["X-My-Header"] = "hello"
req.body = JSON.dump(key: "value")
r2 = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
puts r2.body
# GET — inspect what headers PowerShell adds automatically
$r = Invoke-WebRequest -Uri 'https://catastrophic.io/echo?foo=bar'
$r.Content | ConvertFrom-Json | Select-Object method, headers
# POST with a body
$r = Invoke-WebRequest -Uri 'https://catastrophic.io/echo' `
-Method POST `
-ContentType 'application/json' `
-Body '{"key":"value"}'
$r.Content | ConvertFrom-Json
/echo is the one endpoint that lives on every host. The same handler
serves it at:
https://catastrophic.io/echo(this hub)https://chaos.catastrophic.io/echohttps://not.catastrophic.io/echohttps://bots.catastrophic.io/echo(alias for chaos)
Use it as a fixed point: send a request, inspect the reflection, confirm your client is constructing what you think it’s constructing. Then point the client at the chaos endpoints with confidence that any subsequent failures are the server’s fault, not the client’s.
It’s a debugging tool more than a chaos tool — that’s why it isn’t in the catalog on either chaos or not. But it’s available on all of them so you can sanity-check against whichever host you’re about to test.