Appendix: Making notifyUrl Publicly Reachable
NOTE
This is an optional appendix, not a required step. If you already have a publicly reachable server or domain, use it for notifyUrl and skip this page.
Why this requirement exists
One-key setup asks for two URLs, and they point in opposite directions — which is the single most common mix-up when integrating:
| Field | Who calls whom | Must be publicly reachable |
|---|---|---|
notifyUrl | HashNut server → your server | Yes |
callbackUrl | User's browser → your site | No |
notifyUrl is the address HashNut calls you on once an order has been paid (the webhook). The request originates from HashNut's server, so the address has to be reachable from the public internet — http://localhost:1800 is not.
callbackUrl is only where the user's browser is redirected after paying. The redirect happens in the user's own browser, so http://localhost:5173 works perfectly well.
WARNING
Symptoms of getting these two backwards:
- The payment succeeds but the order status never changes —
notifyUrlpoints at a local address and HashNut cannot reach you - After paying you land on a page that will not load —
callbackUrlpoints at a tunnel address and the tunnel is no longer running
A way to remember it: notify = your backend receives a notification (server to server), callback = your frontend gets a redirect (browser behaviour).
Your options
If you do not have a public server during local development, use a tunnel to expose a local port. Common choices:
| Tool | Notes |
|---|---|
| ngrok | Quickest to get going; on the free tier the address changes on every restart |
| frp | Needs a machine of your own with a public IP; the address is stable |
| Cloudflare Tunnel | Free and can use your own domain; a bit more configuration |
It makes no difference to HashNut which one you use — all it cares about is whether notifyUrl can be reached. The example below uses ngrok.
Example with ngrok
Sign up at ngrok.com, install it as described there, then expose your backend port (the demo shop uses 1800 by default; use whatever you set server.port to):
ngrok http 1800The Forwarding line in the output is the public address you need:
Forwarding https://xxxxx.ngrok-free.dev -> http://localhost:1800With that address in hand, fill in 1.3 Filling in the one-key setup parameters like this:
| Parameter | Local development | Production |
|---|---|---|
notifyUrl | https://xxxxx.ngrok-free.dev/api/notify | https://your-domain/api/notify |
callbackUrl | http://localhost:5173/payment-result | https://your-domain/payment-result |
The paths /api/notify and /payment-result are the demo shop's conventions; use your own routes if you are writing your own application.
WARNING
On the ngrok free tier the address changes every time you restart it. HashNut still has the old address stored, so notifications go to a dead URL — which looks exactly like "the payment succeeded but the order status never changed".
So leave ngrok running while you debug; if the address does change, update the API key's notifyUrl in the dashboard. To avoid this entirely, use a paid ngrok domain, or frp / Cloudflare Tunnel.
Checking that it works
With the backend running, call the demo shop's API through the public address:
curl https://xxxxx.ngrok-free.dev/api/chainsIf it returns the chains and tokens as JSON, the path from the internet to your machine is open and HashNut's notifications will get through.
If it does not, check in this order: is the tunnel still running, is the backend started, do the ports match.
See also
- 1. One-Key Setup — where you fill in
notifyUrl/callbackUrl(section 1.3) - 2.5 Verify the API Key — set up the demo shop and run a real payment to verify notifications