Quickstart
This page takes one test payment from start to finish:
- get your API key and webhook secret,
- create a payment,
- the customer pays on the checkout page,
- the webhook (IPN) arrives,
- your server verifies it and marks the order paid.
Everything runs in test mode: no coins move, and you
simulate what the customer pays. Each step is shown with
curl, and the whole server at the end with the
Node.js and Python libraries.
1. Get your keys
Open an account (or ask your account owner for a test key):
curl -s https://api.payitgo.co/merchant/signup \
-H 'Content-Type: application/json' \
-d '{"merchant_name": "Acme Store", "email": "you@acme.example", "password": "a-long-password"}'
The 201 response holds two API keys, shown only this once:
{
"api_keys": [
{ "api_key": { "id": "4f2a9c1e7b3d5a08c6e1d7b2", "livemode": false, "…": "…" }, "secret": "4f9c2a7e1b8d3f6a0c5e9b2d7a4f1c8e3b6d9a2f5c8e1b4d7a0f3c6e9b2d5a8f" },
{ "api_key": { "id": "8d3b6e2f1a4c9e07b5d2a8c4", "livemode": true, "…": "…" }, "secret": "ea2bc4d2247c7159eec631a51a086a4a6bbd156cedb9982ea99510c5a9f3e0f5" }
],
"…": "…"
}
Keep both on your server. Use the test key (the one with "livemode": false)
for now:
export PAYITGO_BASE_URL=https://api.payitgo.co
export PAYITGO_KEY=4f9c2a7e1b8d3f6a0c5e9b2d7a4f1c8e3b6d9a2f5c8e1b4d7a0f3c6e9b2d5a8f
(The full signup response: Account and team.)
2. Get your webhook secret and set your URL
Your test webhook already exists, with its signing secret:
curl -s $PAYITGO_BASE_URL/v1/webhook -H "Authorization: Bearer $PAYITGO_KEY"
{
"webhook": {
"livemode": false,
"url": null,
"secret": "6e86b3f1…a8418ae0",
"created_at": "2026-10-10T07:48:44.128Z",
"updated_at": "2026-10-10T07:48:44.128Z"
}
}
Keep the secret on your server too (PAYITGO_WEBHOOK_SECRET), then tell payitgo
where to send notifications. The URL must be public and https:// (for a
laptop, use a tunnel such as ngrok):
curl -s -X PUT $PAYITGO_BASE_URL/v1/webhook \
-H "Authorization: Bearer $PAYITGO_KEY" \
-H 'Content-Type: application/json' \
-d '{"url": "https://acme.example/payitgo/ipn"}'
200 OK with the same object, now with "url": "https://acme.example/payitgo/ipn".
Check that your endpoint is reachable with POST /v1/webhook/test: a signed
ping arrives within seconds (Webhooks).
3. Create a payment
When your customer clicks "Pay with crypto", your server creates a payment for the order:
curl -s $PAYITGO_BASE_URL/v1/payments \
-H "Authorization: Bearer $PAYITGO_KEY" \
-H 'Content-Type: application/json' \
-d '{"order_id": "ORD-1001", "amount_usd": "19.99", "success_url": "https://acme.example/thanks"}'
201 Created, shortened:
{
"id": "c41e7a9d2b05f3e8a6d17c90",
"order_id": "ORD-1001",
"status": "pending",
"token": "",
"token_options": [
{ "token": "USDT_TRC20", "chain": "tron", "min_usd": "6.44", "network_fee_usd": "3.22", "fee_note": "Final in about a minute." },
{ "token": "USDT_BEP20", "chain": "bsc", "min_usd": "1", "network_fee_usd": "0.01", "fee_note": "Final in under a minute." }
],
"livemode": false,
"usd_amount": "19.99",
"amount_due_usd": "19.99",
"expires_at": "2026-10-10T10:00:00.832Z",
"checkout_url": "https://pay.example.com/pay/b28d4f61e9a03c75d2e8f1a6",
"…": "…"
}
No token was given, so the customer will choose the coin on the checkout
page. Save id with your order. Sending the same order_id again returns
this same payment (200), so a retried request never makes a second one.
Every field: the payment object.
4. The customer pays
Redirect the customer to checkout_url. The hosted page lets them choose a
coin, then shows the amount, the address and a QR code, counts down the 15
minutes and updates live.
No coins move in test mode: you choose what the customer does. Open
checkout_url yourself and choose a coin (or set it with
POST /v1/payments/{id}/token), then simulate the customer paying in full:
curl -s -X POST $PAYITGO_BASE_URL/v1/payments/c41e7a9d2b05f3e8a6d17c90/simulate \
-H "Authorization: Bearer $PAYITGO_KEY" \
-H 'Content-Type: application/json' \
-d '{"outcome": "confirmed"}'
200 OK with the payment, now with "simulation": {"outcome": "confirmed", "state": "running"}.
A few seconds later the deposit arrives and the checkout page moves to
detecting, then to confirmed, as it would for real coins. To start the
simulation without calling again, create the payment with
"simulate": "confirmed": it plays as soon as the coin is chosen. Partial
payments, expiry, overpayment and the other outcomes:
Test mode.
Never send real coins to a test payment's address: nothing watches it.
Want the amount, address and QR code on your own page, in your own design? See Your own payment page.
5. The webhook arrives
Your URL receives payment.detecting (money seen, confirming), then
payment.confirmed (final). In a simulation they come seconds apart; a live
payment's come as fast as its network confirms (Currencies):
POST /payitgo/ipn HTTP/1.1
Content-Type: application/json
User-Agent: payitgo-webhooks/1.0
X-Webhook-Id: 6a8c0e2b4d1f3a5c7e9b0d2f
X-Webhook-Event: payment.confirmed
X-Webhook-Attempt: 1
X-Webhook-Signature: t=1791619398,v1=5f1c…(64 hex)
{"created_at":"2026-10-10T09:04:03.771240512Z","data":{"id":"c41e7a9d2b05f3e8a6d17c90","order_id":"ORD-1001","status":"confirmed","livemode":false,…},"id":"2b8f4d6a0c1e3f5a7b9d2c4e","livemode":false,"type":"payment.confirmed"}
data is the payment. Every header and field:
the notification.
6. Verify it and mark the order paid
Before trusting the notification, check its signature with your webhook
secret: v1 must equal the hex HMAC-SHA256 of t + "." + raw body, and t
must be within 5 minutes. The libraries do this in one call (below);
Webhooks shows it by hand in five
languages.
Then:
- skip the event if you already processed its
id(notifications can arrive twice); - on
payment.confirmed, find the order bydata.order_idand mark it paid; - answer
200within 10 seconds. Anything else is retried, for up to a day and a half.
The whole integration
Node.js
npm install @payitgo/node express
// server.js
import express from "express";
import { Payitgo } from "@payitgo/node";
const payitgo = new Payitgo(process.env.PAYITGO_KEY, {
baseUrl: process.env.PAYITGO_BASE_URL,
webhookSecret: process.env.PAYITGO_WEBHOOK_SECRET,
});
const app = express();
// 3. Create the payment and send the customer to pay.
app.post("/checkout", express.json(), async (req, res) => {
const order = await orders.create(req.body); // your code
const payment = await payitgo.payments.create({
order_id: order.id,
amount_usd: order.totalUsd, // "19.99"
success_url: "https://acme.example/thanks",
cancel_url: "https://acme.example/cart",
});
await orders.setPaymentId(order.id, payment.id);
res.redirect(303, payment.checkout_url);
});
// 5–6. Receive the IPN, verify it, mark the order paid. Keep the body raw.
app.post("/payitgo/ipn", express.raw({ type: "application/json" }), async (req, res) => {
let event;
try {
event = payitgo.webhooks.constructEvent(req.body, req.get("X-Webhook-Signature"));
} catch {
return res.sendStatus(400); // not from payitgo, or tampered with
}
if (!(await events.seen(event.id))) {
if (event.type === "payment.confirmed") {
await orders.markPaid(event.data.order_id); // idempotent
}
await events.remember(event.id);
}
res.sendStatus(200);
});
app.listen(3000);
Python
pip install payitgo flask
# server.py
import os
import payitgo
from flask import Flask, redirect, request
client = payitgo.Client(
os.environ["PAYITGO_KEY"],
base_url=os.environ["PAYITGO_BASE_URL"],
webhook_secret=os.environ["PAYITGO_WEBHOOK_SECRET"],
)
app = Flask(__name__)
# 3. Create the payment and send the customer to pay.
@app.post("/checkout")
def checkout():
order = orders.create(request.json) # your code
payment = client.payments.create(
order_id=order.id,
amount_usd=order.total_usd, # "19.99"
success_url="https://acme.example/thanks",
cancel_url="https://acme.example/cart",
)
orders.set_payment_id(order.id, payment.id)
return redirect(payment.checkout_url, code=303)
# 5-6. Receive the IPN, verify it, mark the order paid. Use the raw body.
@app.post("/payitgo/ipn")
def ipn():
try:
event = client.webhooks.construct_event(request.get_data(), request.headers.get("X-Webhook-Signature"))
except payitgo.SignatureVerificationError:
return "", 400 # not from payitgo, or tampered with
if not events.seen(event.id):
if event.type == "payment.confirmed":
orders.mark_paid(event.data.order_id) # idempotent
events.remember(event.id)
return "", 200
PHP, Go and Dart: Libraries.
Go live
- The account owner turns live mode on:
POST /merchant/live {"enabled": true}(Account and team). - With the live key, read the live webhook's secret (
GET /v1/webhook) and set its URL (PUT /v1/webhook). Live and test have separate URLs and separate secrets. - Deploy the live key and the live webhook secret. Remove any
simulatefrom your payment requests: a live key refuses it. - Add a payout address for each coin you accept, so payments reach your wallet as they are. A coin you have none for is converted into one you have, which costs more (Fees and settlement).
- Make one small real payment end to end.