local-webhooks

A small webhook receiver for development and testing. It accepts GET and POST requests, returns a deterministic response ID (the MD5 of the raw request body), and can mimic a couple of Svix Play response options — echoing bodies back, forcing failure status codes, and failing a random percentage of requests.

Send requests to any non-root path of https://webhooks.leonardofaria.net, e.g. /webhook/ — the root path serves this page.

Sending Webhooks

curl -X POST https://webhooks.leonardofaria.net/webhook/ \
  -H 'content-type: application/json' \
  -d '{"event":"created"}'

Successful responses return the MD5 of the raw request body:

{ "webhook_response_id": "e18ea42a5b23a8989f556dc23ec73749" }

With echo_body=true or echo=body, object request bodies are merged with the generated response ID:

curl -X POST 'https://webhooks.leonardofaria.net/webhook/?echo_body=true' \
  -H 'content-type: application/json' \
  -d '{"hello":"world"}'
{
  "hello": "world",
  "webhook_response_id": "fbc24bcc7a1794758fc1327fcfebdaf6"
}

Non-object request bodies are returned under body because there is no object to merge into:

curl -X POST 'https://webhooks.leonardofaria.net/webhook/?echo_body=true' -d 'hello'
{
  "webhook_response_id": "5d41402abc4b2a76b9719d911017c592",
  "body": "hello"
}

Object-like payloads such as { a: "b" } are accepted too:

curl -X POST 'https://webhooks.leonardofaria.net/webhook/?echo_body=true' \
  -H 'content-type: application/json' \
  -d '{ a: "b" }'
{
  "a": "b",
  "webhook_response_id": "c80eec3dbe6421d3768baf181aaf24ac"
}

Loosely-formatted bodies with unquoted keys and unquoted string values (common when a templating engine renders into the body without quoting, e.g. { city: {{ customer.city }} }) are parsed leniently — unquoted multi-word values get wrapped in strings before merging:

curl -X POST 'https://webhooks.leonardofaria.net/webhook/?echo_body=true' \
  -H 'content-type: application/json' \
  --data-binary $'\n\n{ city: bbbb, a: "b", c: 1 }'
{
  "city": "bbbb",
  "a": "b",
  "c": 1,
  "webhook_response_id": "25b54ba83a1362ed4e5dcb3d00d3ebf8"
}

GET Requests

Any path also accepts GET requests, including subpaths like /webhook/whatever. A bare GET returns the response id just like an empty POST:

curl https://webhooks.leonardofaria.net/webhook
{ "webhook_response_id": "d41d8cd98f00b204e9800998ecf8427e" }

Query params are merged into the response, the same way echo_body merges object bodies for POST:

curl 'https://webhooks.leonardofaria.net/webhook/whatever?foo=bar&baz=qux'
{
  "foo": "bar",
  "baz": "qux",
  "webhook_response_id": "3229135643aff3d4c314486e8945ad47"
}

force_status_code and random_failure_rate work on GET requests too.

Query Parameters

Parameter Description
echo_body=true or echo=body Merge object request bodies into the JSON response with webhook_response_id.
random_failure_rate=0..100 Percentage of requests that should fail randomly. Failed requests return 400 unless force_status_code is set.
force_status_code=400..599 Force this status on responses. Alone, every request returns this code with an empty body. Paired with random_failure_rate, only the failing slice uses it.

Examples:

curl -X POST 'https://webhooks.leonardofaria.net/webhook?echo_body=true' -d 'hello'
curl -X POST 'https://webhooks.leonardofaria.net/webhook?echo=body' -d '{"ok":true}'
curl -X POST 'https://webhooks.leonardofaria.net/webhook?random_failure_rate=30' -d 'hello'
curl -X POST 'https://webhooks.leonardofaria.net/webhook?force_status_code=404' -d 'hello'
curl -X POST 'https://webhooks.leonardofaria.net/webhook?random_failure_rate=30&force_status_code=503' -d 'hello'

Response Headers

Every webhook response (success and forced failure) includes: