Skip to content

2.5 Verify the API Key ​

The previous steps verified the split contract and the fund flow, all through the HashNut dashboard. This step switches to your own server for placing orders, verifying that the API key produced by one-key setup can create orders, query them, and receive HashNut's payment notifications.

The quickest way is to run the demo shop — a minimal working integration you can copy from.

NOTE

The demo shop comes in a Go and a Java version with identical behaviour; pick one. Both share the same React frontend and the same migrate.sql, both listen on port 1800, and both use the demo_shop database.

2.5.1 Requirements ​

DependencyVersionNotes
DockerAny recent versionUsed to run PostgreSQL so you do not have to install it locally
Node.js18+Frontend
Go1.21+Only for the Go backend
Java + MavenJava 11+ / Maven 3.6+Only for the Java backend
A tunnel—Local development only, so HashNut can reach your machine with payment notifications. ngrok / frp / Cloudflare Tunnel all work — see the appendix

2.5.2 Clone the code ​

Go version:

bash
git clone https://github.com/nuttybounty/hashnut-demo-go.git
cd hashnut-demo-go

Java version:

bash
git clone https://github.com/nuttybounty/hashnut-demo.git
cd hashnut-demo

NOTE

The Java version pulls the HashNut SDK from JitPack automatically — nothing to install by hand.

2.5.3 Start PostgreSQL with Docker ​

bash
docker run -d \
  --name hashnut-demo-pg \
  -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_DB=demo_shop \
  -p 5432:5432 \
  postgres:16

These values are the defaults the backend expects. If you change them here, change the backend config to match:

ArgumentValueBackend setting
Usernamepostgres (image default)user / username
POSTGRES_PASSWORDpostgrespassword
POSTGRES_DBdemo_shopdbname / the database in the JDBC url
-p 5432:5432Mapped to local 5432host / port

Check that the container is up:

bash
docker exec hashnut-demo-pg pg_isready -U postgres

accepting connections means you are good.

NOTE

POSTGRES_DB=demo_shop creates the database on the container's first start, so there is no need to run CREATE DATABASE yourself. If you are reusing an existing container that variable has no effect and you will need to create it manually: docker exec -i hashnut-demo-pg psql -U postgres -c "CREATE DATABASE demo_shop;"

2.5.4 Put the account info into migrate.sql ​

Open migrate.sql in the repository root, find the t_hashnut_api_key block, and replace the placeholders with the values exported in 1.4 Save the account info.

The exported TXT already ends with SQL you can paste directly:

sql
INSERT INTO t_hashnut_api_key (block_chain, splitter, access_key_id, secret_key) VALUES
    ('ETH', '0xbd09...fca', '01...YW', 'H1...Tt')
ON CONFLICT (block_chain) DO UPDATE SET
    splitter = EXCLUDED.splitter,
    access_key_id = EXCLUDED.access_key_id,
    secret_key = EXCLUDED.secret_key;

WARNING

The two rows shipped in migrate.sql are placeholders (${split address} / secret-key). Running them as-is makes order creation fail signature verification. One run of one-key setup covers one chain, so if you want both ETH and TRON, switch wallets in the client and run it twice, then paste both statements.

Also check the t_coin_info block and make sure the chains and tokens are the ones you actually use — that table decides which chains the payment page offers.

Then apply it:

bash
docker exec -i hashnut-demo-pg psql -U postgres -d demo_shop < migrate.sql

Verify the rows landed:

bash
docker exec -i hashnut-demo-pg psql -U postgres -d demo_shop -c "select block_chain, splitter, access_key_id from t_hashnut_api_key;"

2.5.5 Configure and start the backend ​

Go version — edit etc/application.yaml:

yaml
server:
  port: 1800

database:
  host: "localhost"
  port: 5432
  user: "postgres"
  password: "postgres"
  dbname: "demo_shop"
  sslmode: "disable"

hashnut:
  testMode: false    # always false — production
  baseURL: ""        # leave empty for the default
bash
go run main.go

Java version — edit src/main/resources/application.yml:

yaml
server:
  port: 1800

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/demo_shop
    username: postgres
    password: postgres

hashnut:
  test-mode: false   # always false — production
  base-url: ""       # leave empty for the default
bash
mvn spring-boot:run

Either way the backend starts on http://localhost:1800. Check that it reached the database:

bash
curl http://localhost:1800/api/chains

It should return the chains and tokens you configured in t_coin_info.

WARNING

testMode / test-mode must stay false. Setting it to true points the SDK at a different backend address where your API key does not exist, and order creation fails outright.

2.5.6 Confirm notifyUrl is reachable ​

notifyUrl is the address HashNut calls you on, so it must be publicly reachable. Use your public server if you have one; during local development use a tunnel, as described in Appendix: Making notifyUrl publicly reachable.

All you need to check here is that the address currently works:

bash
curl https://your-public-address/api/chains

Chains and tokens coming back means it is open. On the ngrok free tier note that the address changes after a restart; when it does, update the API key's notifyUrl in the dashboard, otherwise notifications go to a dead address — which looks like "the payment succeeded but the order status never changed".

2.5.7 Start the frontend ​

The frontend is a separate repository, shared by both backends:

bash
git clone https://github.com/nuttybounty/hashnut-demo-web.git
cd hashnut-demo-web
npm install
npm run dev

It starts on http://localhost:5173 and Vite proxies /api to localhost:1800, so there is no CORS setup to do.

2.5.8 Run a full payment ​

Open http://localhost:5173 and:

  1. Pick any product and click Pay with Crypto
  2. Choose a chain and token (the ones from t_coin_info)
  3. You are taken to the HashNut payment page — pay with your wallet
  4. You are redirected back to http://localhost:5173/payment-result showing the payment succeeded

The whole path looks like this:

Browser (localhost:5173)
  → POST /api/orders          (Vite proxies to localhost:1800; the backend uses the API key to create the order)
  → redirect to the HashNut payment page
  → user pays on-chain
  → HashNut server → tunnel → localhost:1800/api/notify        (notifyUrl: your backend is notified)
  → HashNut payment page → localhost:5173/payment-result?state=4  (callbackUrl: your frontend redirect)

Getting through this means order creation, order queries and callback notifications all work with your API key, which is what this section set out to prove.

Where to look when it goes wrong ​

SymptomLikely cause
Order creation fails with a signature errormigrate.sql still has the placeholders, or a character was lost copying accessKey / secretKey
"API key does not exist"testMode / test-mode is not set to false
The payment page offers no chainsThat chain is missing from t_coin_info, or the backend never reached the database
Payment succeeds but the order status never changesnotifyUrl is unreachable: the tunnel is down, the address changed and was not updated, or notifyUrl and callbackUrl are the wrong way round
Order rejected with an IP errorThe API key's bindIp is neither * nor your egress IP

2.5.9 Next ​

The full API list, database schema and deployment notes live in the two repositories' READMEs:

To adapt this to your own application, the three places to look at in the backend are order creation, order queries, and the signature check and handling in /api/notify. See Create Order, Query Order and Notifications.