
Sixty containers on one server
One bare-metal box runs dozens to hundreds of Hoody containers. KSM and BTRFS dedup make the marginal cost near zero.
Stripe POSTs the event body to a pipe path with ?n=12. Twelve subscribers GET the same path with ?n=12. The pipe holds the message until everyone is connected, then streams to all twelve at once. No broker, no consumer group, no DLQ.
Hoody Pipe is HTTP streaming with a multi-receiver mode. Append ?n=N to both the sender and the receiver URLs and the pipe waits until N readers attach, then mirrors the body to all of them simultaneously. The slow ones throttle their own connection — the fast ones keep flowing.
Your Stripe endpoint forwards the event body straight to /api/v1/pipe/billing?n=12. The connection blocks while the pipe waits for the twelve receivers to assemble.
Each subscriber is a curl loop in a container, GETing the same path with ?n=12. The pipe holds the body in memory until the twelfth reader connects — no on-disk queue, nothing to flush.
Once everyone is connected, the body streams to all twelve at once. A slow reader applies backpressure to its own socket; the others keep going. When the last reader disconnects, the pipe forgets the message.
# Sender side — your Stripe webhook handler.
# Pipe holds the body until 12 readers are attached.
curl -X POST "https://api.hoody.com/api/v1/pipe/billing?n=12" \
-H "Authorization: Bearer $HOODY_TOKEN" \
-H "Content-Type: application/json" \
--data-binary "@stripe-event.json"
# Receiver side — one of twelve subscriber containers.
# Same path, same n. Streams the event body when fan-out is ready.
curl -N "https://api.hoody.com/api/v1/pipe/billing?n=12" \
-H "Authorization: Bearer $HOODY_TOKEN" | ./billing-handler.sh
# n must match on both ends — mismatch returns 400.
# Default n=1 is point-to-point. n=12 is fan-out for twelve.Same URL, same query string, both ends. The sender's connection is the broker; the readers' connections are the consumer group. Backpressure is per-socket, not per-topic, because there is no topic — there is one in-flight body and twelve sockets pulling on it.
Adding a subscriber is one more curl in one more container. Removing one is killing the curl. There is no broker config, no consumer-group rebalancing, no DLQ to drain — the cluster topology is just whatever processes happen to be running right now.
The pipe doesn't track who is subscribed. It just waits for n connections per event. Bump n on both ends and the next event waits for the new headcount. There is no membership state to corrupt because there is no membership.
Every line below is a category of work that disappears when the broker is the protocol. No infrastructure to provision, no abstractions to learn — just curl and ?n.
No SQS, no Kafka cluster, no RabbitMQ exchange. The pipe is the queue, and it lives for one message at a time.
No offsets, no commits, no rebalances. The pipe holds the body until n sockets attach — that is the entire coordination model.
If the sender disconnects without n readers ready, the pipe times out (5-min TTL) and Stripe retries. No poison-message bucket to babysit.
Subscribers are curl loops. Senders are curl. The protocol is HTTP. Anything that can hit a URL can join the cluster.
A slow reader throttles its own connection. The other eleven keep streaming at full speed. There is no head-of-line blocking across the fleet.
The body is gone the moment the last reader disconnects. No retention policy to set, no log-compaction to schedule, no GDPR delete-on-request job to write.
Twelve subscribers, one URL, no broker.
The brokers below are the things you used to install before HTTP fan-out was a query parameter. The right side is what replaces them — one path, one n, and twelve curl processes that don't know they're a cluster.
Twelve receivers GET the same path with the same n. The pipe is the broker — and forgets the message the second the last reader hangs up.
If you reach for any of these to broadcast a webhook to N consumers, the pipe model is doing the same job in two curl invocations — sender on one side, receivers on the other.
The fan-out you used to write a sprint for is now a query parameter. Append ?n=12 and ship.