content-type application/json; charset=utf-8 user-agent Stripe/1.0 (+https://stripe.com/docs/webhooks) stripe-signature t=1745345528,v1=3a9bf41cd9… x-forwarded-for 3.18.12.63
{
"id": "evt_3PqXf2A7BkN",
"type": "invoice.paid",
"created": 1745345528,
"data": {
"object": {
"amount_paid": 2400,
"currency": "usd",
"customer": "cus_QmVb3LN4P"
}
}
} Every request, headers and all. Decoded bodies, verified signatures, full timing. Never truncated, never summarized.
$ notunnel inspect req_01HQZ4K9 One durable inbox for every webhook from every source. Stripe, GitHub, Shopify, Twilio, your own services — all in one record.
POST acme.notunnel.sh/hooks/stripe Resend any request — or a filtered range — to localhost, staging, or anywhere. Rewrite the body in flight: swap prod ids, override keys, fix the signature.
✦ the part nobody else does well$ notunnel replay req_01HQZ4K9 --to :3000 Ninety days of full-text searchable history. Export to S3 — ours or yours. On Publisher, your payloads never leave your infrastructure.
$ notunnel search --since 30d invoice.paid Tunnels forward one webhook to one laptop, and only while that laptop is awake, on that network, running that process. Miss the window, miss the event. We keep the webhook.
acme.notunnel.sh is yours. No more re-registering
a fresh URL with every vendor on every restart.
{
"type": "invoice.paid",
"data": {
"object": {
"customer": "cus_QmVb3LN4P",
"amount_paid": 2400,
"status": "paid"
}
}
} $ notunnel replay req_01HQZ4K9 \ --to localhost:3000 \ --set data.object.customer=cus_local_test \ --set data.object.amount_paid=100 \ --resign stripe
{
"type": "invoice.paid",
"data": {
"object": {
"customer": "cus_local_test",
"amount_paid": 100,
"status": "paid"
}
}
}
// stripe-signature re-signed with your local webhook secret Swap prod ids for local fixtures, drop amounts to test edge cases, re-sign with your local webhook secret — all without touching the recorded event. The receipt stays immutable. The replay is whatever you need it to be.
Put your name on the list. We'll webhook your inbox when we ship — and your signup will already be waiting in your notunnel inbox the moment you sign in.
$ curl https://notunnel.sh/waitlist \ -H 'content-type: application/json' \ -d '{"email":"you@company.com"}'
{
"event": "waitlist.joined",
"email": "you@company.com",
"inbox": "b3f-waitlist.notunnel.sh",
"message": "We'll webhook your inbox when we ship."
} The CLI treats your inbox like a log file that knows JSON. Filter by source, status, event, time, or a jq-style predicate on the body. Replay a single request or a whole range — the same grammar works either way.
~/acme $ notunnel tail --source stripe --status 2xx --since 1h → listening to acme-prod · 3 filters 14:32:08.412 200 POST /hooks/stripe invoice.paid 142ms 14:32:08.104 200 POST /hooks/stripe charge.succeeded 98ms 14:28:41.003 200 POST /hooks/stripe customer.updated 61ms ──────────────────────────────────────────── ~/acme $ # bulk replay: every failed stripe webhook, last 2h, to local ~/acme $ notunnel replay --source stripe --status 5xx --since 2h \ --to localhost:3000 --resign stripe → replaying 7 requests to localhost:3000 ✓ req_01HQZ4K9 invoice.paid 200 · 38ms ✓ req_01HQZ50W invoice.payment_failed 200 · 41ms ✓ req_01HQZ512 charge.refunded 200 · 29ms ... ──────────────────────────────────────────── ~/acme $ # filter on the body itself — jq-style predicate ~/acme $ notunnel search --body '.data.object.amount_paid > 10000' \ --since 24h → 3 matching requests req_01HQZ4K9 invoice.paid $120.00 req_01HQYX4L invoice.paid $480.00 req_01HQYN82 invoice.payment_failed $1,200.00
No numbers yet — we want to learn what works before we publish them. But we've already decided what pricing will and won't be. Consider this the pre-publication draft, in the shape of the kind of response the product will send.
{ "model": "flat", // never per-seat "free_forever": true, // generous quota, in perpetuity "own_your_logs": true, // take them with you when you leave "per_seat_math": false, "per_request_anxiety": false, "retention_paywall": false, "sales_gate": false, "promise": "Whatever the number is, you won't be surprised.", "published": "at launch" }
The finalized version ships — as a webhook, naturally — to everyone on the waitlist the day we launch.