Building Your First Node
This is the authoritative hands-on walkthrough for getting the Merchant Template running on your machine. The journey map (Your First PayPort Node) explains *why* each stage exists; this page teaches *how* to do the setup steps without guessing.
You deploy an independent FastAPI Node on infrastructure you operate. Labs does not start a process for you when you claim a seat.
Where you are going
Keep this map in mind. After each section, you should know which box you are in.
Merchant Template on GitHub
↓
Your own repository (Use this template)
↓
Code on YOUR computer (or YOUR VM)
↓
Python virtual environment
↓
Dependencies installed
↓
Your .env configuration
↓
Node runs on localhost (local test only)
↓
/health + agent.json + 402 work
↓
Deploy publicly (Railway / your VM / other host)
↓
Host gives you a public HTTPS URL
↓
Register that URL with PayPort Labs
↓
Node appears in My Nodes
Part One keeps PAYMENT_MODE=mock. Settlement on Base Sepolia is Part Two on the journey map — not required to finish this page.
Where do I type these commands?
These instructions use a terminal (also called a command line, shell, or Console).
A terminal is a text window where you type commands and the computer runs them.
Which computer is executing the command?
Always answer that before you type.
| Place | What it means |
|---|---|
| Your computer | Terminal app on Windows / macOS / Linux. Beginner path for Part One local testing. |
| Your VPS/VM | You first connect with SSH; *then* commands run on that remote server. Optional later — not required. |
| Railway (or similar host) | Often configured in a web UI that builds from your GitHub repo. You may not type uvicorn there the same way. |
Do not mix them up.
- Local setup commands below assume: you are in a terminal on the machine where you want the code to live for local testing (usually your laptop).
- Railway is a hosting platform, not part of the PayPort Protocol. It is one way to get a public HTTPS URL.
- You do not need to buy a VM. You do need a public deployment eventually.
Opening a terminal (quick pointers)
- macOS: Terminal (Spotlight → “Terminal”), or the terminal panel in VS Code / Cursor.
- Windows: PowerShell, Windows Terminal, or the terminal in VS Code / Cursor.
If a command below starts with source, you are on a Unix-style shell (macOS, Linux, or Git Bash / WSL on Windows). On plain PowerShell the activate line differs — see the note under “Activate the environment.”
- Linux / VPS: any shell after you log in (or after SSH).
Who hosts this Node?
You do.
You build from the Merchant Template
↓
You deploy to Railway / your VM / another public host
↓
Host gives you a public HTTPS base URL
↓
You register that URL with Labs
Labs Reference Demo — not your deployment: Labs Echo :8142, HVAC :8141, monorepo district folders. Do not register those URLs.
Golden Rule
Do not edit app/main.py or app/core/security.py. Those enforce HTTP 402 and the agent manifest.
You customize app/service.py and your private .env.
Step 0 — Create your own GitHub repository
What you're doing
Making a personal copy of the Merchant Template that you own. Later, Railway (or another host) should deploy your repository — not treat the official PayPort template as your app forever.
In the browser (not the terminal yet)
- Open payport-merchant-template
- Click Use this template → Create a new repository
- Name it something yours (e.g.
my-practice-echo) - Create the repository under your GitHub account
PayPort Merchant Template (official)
↓
Use this template
↓
Your own GitHub repository
↓
git clone YOUR_REPO ← next step
How to know it worked
GitHub shows a new empty-looking project that already contains the Merchant Template files, under your username — not forever working inside pgwilde8/payport-merchant-template.
Step 1 — Download your code (git clone)
What you're doing
Copying your repository from GitHub onto the computer where your terminal is running.
Command (replace with your real GitHub URL):
git clone https://github.com/YOUR_GITHUB/YOUR_REPO.git
What just happened
git clone downloads a full copy of that repository into a new folder named after the repo.
How to know it worked
A new folder exists (e.g. my-practice-echo). You can list it:
ls
You should see your project directory name in the list.
Step 2 — Enter the project folder (cd)
What you're doing
Moving the terminal’s “current folder” into the project so later commands operate on the right files.
Command
cd YOUR_REPO
Example: cd my-practice-echo
What just happened
cd means change directory. Your prompt is now “inside” the project.
How to know it worked
pwd
should end with your project folder name. You should also see files such as requirements.txt, .env.example, and an app/ directory:
ls
Step 3 — Create a Python virtual environment
What you're doing
Creating a private Python toolkit for this Node only, so its packages do not collide with other Python software on the machine.
Command
python3 -m venv venv
What just happened
Python created a folder named venv/ inside your project. That folder holds an isolated interpreter and package set.
How to know it worked
ls
should now include a venv directory.
Step 4 — Activate the virtual environment
What you're doing
Turning that private environment on for this terminal session.
Command (macOS / Linux / WSL / Git Bash)
source venv/bin/activate
Windows PowerShell (if you are not using Git Bash / WSL)
.\venv\Scripts\Activate.ps1
What just happened
source (or the PowerShell activate script) switches this terminal so python and pip refer to the project’s environment.
How to know it worked
Your prompt often shows (venv) at the start, for example:
(venv) ... $
If you open a new terminal window later, activate again — activation is per session.
Step 5 — Install dependencies
What you're doing
Installing the Python packages this Node needs (FastAPI, uvicorn, and the rest listed by the template).
Command
pip install -r requirements.txt
What just happened
requirements.txt is a shopping list of packages. pip install -r installs everything on that list into your active virtual environment.
How to know it worked
The command finishes without a fatal error. You can spot-check:
python -c "import fastapi; print('ok')"
should print ok.
Step 6 — Create your private .env file
What you're doing
Creating your Node’s private configuration file from the safe example the template ships.
Command
cp .env.example .env
On Windows PowerShell (if cp is unavailable):
Copy-Item .env.example .env
What just happened
.env.example is a public example. .env is your working config. The app reads .env for wallets, mode, ports, and similar settings.
What is .env?
Applications often load settings from environment variables (named values outside the source code). A .env file is a convenient way to set those for local development.
Your .env is private. Do not commit it to GitHub or paste secrets into public chats.
The template’s .gitignore is meant to keep .env out of git — still double-check you never force-add it.
What to change for Part One
Open .env in any editor. Set at least:
DEVELOPER_WALLET_ADDRESS=0xYourBuilderWallet
PAYMENT_MODE=mock
ENVIRONMENT=village
MERCHANT_BASE_URL=http://127.0.0.1:8124
| Setting | Meaning for Part One |
|---|---|
DEVELOPER_WALLET_ADDRESS | Your Builder Wallet. Becomes agent.json → recipient_wallet. Must match Labs SIWE. |
PAYMENT_MODE=mock | Simulated settlement for the first exercise. |
ENVIRONMENT=village | Village teaching path. |
MERCHANT_BASE_URL | Where this Node believes it lives. Use localhost while testing locally; change to your public HTTPS URL after deploy. |
MERCHANT_AMOUNT | Price in atomic units (6 decimals). 50000 = 0.05 tUSDC. Do not put 0.05 here. |
Builder triangle (must match later at registration):
SIWE wallet = DEVELOPER_WALLET_ADDRESS = agent.json recipient_wallet
Optional template alias only: PAYMENT_RECEIVER_WALLET → DEVELOPER_WALLET_ADDRESS. Builders should set DEVELOPER_WALLET_ADDRESS.
Do not look for MERCHANT_RECIPIENT in the Merchant Template. That name is for Labs Reference Nodes only.
How to know it worked
.env exists beside .env.example. After you start the Node (next steps), agent.json shows your wallet in recipient_wallet.
Step 7 — (Optional now) Customize app/service.py
The template ships with an example dental service so the plumbing runs out of the box.
For District Zero Practice Echo, follow the journey map and the Practice Echo prompt card (District Zero): implement a simple echo paid route. You still only edit app/service.py (and related schemas if needed) — not main.py / security.py.
You can smoke-test the template as-shipped first, then adapt the service for Echo before you register.
Step 8 — Run the Node locally
What you're doing
Starting the FastAPI app so it listens for HTTP requests on this machine only.
Command (venv still active):
uvicorn app.main:app --host 127.0.0.1 --port 8124 --reload
What this means
| Piece | Meaning |
|---|---|
uvicorn | Program that runs your FastAPI app. |
app.main:app | “Load the app object from app/main.py.” |
127.0.0.1 | localhost — only this computer can reach it. |
port 8124 | The door number on that machine. |
--reload | Restart when you edit code (handy while learning). |
Important: localhost is not your registration URL
http://127.0.0.1:8124 is for your testing.
PayPort Labs needs a public HTTPS base URL from a host you operate (Railway, your VM, etc.). You cannot register localhost as your Node.
How to know it worked
The terminal stays open and shows the server is running (often “Uvicorn running on http://127.0.0.1:8124”). Leave it running and open a second terminal for checks (activate venv again in that second window).
Step 9 — Success checks (local)
With the server running, verify each layer.
Health
curl http://127.0.0.1:8124/health
Or open that URL in a browser.
Expect: a successful JSON health response (status ok / healthy — not a connection error).
Manifest
curl http://127.0.0.1:8124/.well-known/agent.json
Expect: JSON including recipient_wallet equal to the wallet you set in .env.
Unpaid paid-route (expect HTTP 402)
Calling a paid route without payment proof should return 402 Payment Required. That means the gate is working — not that your Node is broken.
Confidence script
From the project folder with venv active:
python test_node.py
python test_node.py --mock
Expect: output ending with something like: Your merchant node is PayPort-compatible.
Step 10 — Deploy, then register (next stages)
When local checks pass:
- Deploy your repository to a public host you operate (beginner path: Railway).
Details: Deploy Your Node.
- The host gives you a public HTTPS base URL (example shape:
https://my-practice-echo.up.railway.app). - Set
MERCHANT_BASE_URLon the host to that URL. - Open that URL’s
/healthin a normal browser (not only on your laptop). - Register that URL in District Zero / claim — not Labs
:8141/:8142.
Full “why” for URL and wallets: Your First PayPort Node.
Wallet triangle again:
SIWE session = DEVELOPER_WALLET_ADDRESS = agent.json recipient_wallet
Repository layout (reference)
YOUR_REPO/
├── .env.example # Safe public example — copy to .env
├── .env # YOUR private config — never commit
├── Dockerfile
├── README.md
├── requirements.txt # Package list for pip
├── test_node.py
├── app/
│ ├── main.py # Dual-Mode + 402 gate (locked)
│ ├── config.py
│ ├── service.py # ← primary file you edit
│ └── ...
└── scripts/
└── register_first_node.py
After you are on the map
| System | What it does |
|---|---|
| Town Crawler (~300s) | Health + schema-valid 402 probe on your URL |
| Buyer Bot (~120s) | Mock X-Payment-Proof purchases while PAYMENT_MODE=mock |
| My Nodes | lab.payportdirect.com/dashboard/nodes |
| Map | lab.payportdirect.com/town |
Dual-Mode reminder
| Path | Auth | SMS |
|---|---|---|
| Village | Unpaid → 402; paid → X-Payment-Proof | Always virtual |
| Live client | X-PayPort-Client-Key + Idempotency-Key | Twilio only if ENVIRONMENT=production and Twilio is set |
Issue live keys: POST /admin/client-keys with X-Admin-Token = your SESSION_SECRET.
Next
- Your First PayPort Node — journey map (District Zero → commercial, Part Two settlement)
- Deploy Your Node — localhost vs public HTTPS
- Dual-Mode Merchant Engine (required)
- Glossary