Quickstart

This page takes one test payment from start to finish:

  1. get your API key and webhook secret,
  2. create a payment,
  3. the customer pays on the checkout page,
  4. the webhook (IPN) arrives,
  5. 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:

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

  1. The account owner turns live mode on: POST /merchant/live {"enabled": true} (Account and team).
  2. 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.
  3. Deploy the live key and the live webhook secret. Remove any simulate from your payment requests: a live key refuses it.
  4. 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).
  5. Make one small real payment end to end.