pymnt-forward
v0.1.0
Published
Deliver captured webhooks to a URL on your own machine — the localhost half of a webhook capture endpoint.
Maintainers
Readme
pymnt-forward
Deliver webhooks captured at a tools.moveahead.tech endpoint to a URL on your
own machine.
npx pymnt-forward fwd_YOUR_TOKEN --to http://localhost:3000/webhooksWhy this exists
A capture endpoint can replay a delivery to any address on the public internet.
It cannot replay to localhost:3000, because that address means something
different on every machine — including ours. This program runs where localhost
means your machine.
That inversion is also the reason to prefer this over a tunnel. A tunnel points the internet at your laptop and stops working the moment you close it. Here the endpoint keeps capturing whether or not anything is listening, so whatever arrived while you were away is still there when you start forwarding.
Usage
pymnt-forward <fwd_token> --to <url> [options]
--to <url> where to POST each delivery (required)
--from-start replay everything already captured, not just new arrivals
--interval <secs> how often to poll (default 2)
--api <origin> the capture host (default https://tools.moveahead.tech)
--helpThe token can also come from PYMNT_FORWARD_TOKEN.
Tokens
The forwarder token (fwd_…) is not the endpoint URL token (hk_…), and
the difference is deliberate. The hk_ token lives in a URL: it gets pasted
into gateway dashboards, into Slack while debugging, into config files. If it
also authorised reading stored payloads, every one of those places would become
a place that can read your webhook bodies.
So reading needs its own credential. Generate one from the endpoint's page, keep it out of URLs, and rotate it whenever you like — rotating costs nobody a gateway reconfiguration.
What gets sent
The exact bytes that arrived, with the sender's headers, using the method the gateway used. Headers your capture host added on the way in are stripped, and that decision is made server-side rather than here — this program deliberately knows nothing about which headers are whose, so it cannot drift from what the dashboard shows you.
Your own signature check should therefore pass on a forwarded delivery exactly as it would on the original. If it does not, that is a real finding rather than an artefact of forwarding.
Failures
A delivery your local service refuses or times out on is reported and dropped,
not retried. A dev server that is restarting would otherwise collect a queue of
retries that arrive out of order — a bug this program would have invented in
your debugging session. Use --from-start to replay again once it is back up.
Losing the connection to the capture API is not fatal: it backs off and keeps trying, because this is meant to be left running.
