Account and team

Everything about your account (team, stores, settings, the webhook's signing secret, balances and payouts) is managed through the dashboard API under /merchant, signed in as a person. Your servers only ever need an API key and the /v1 routes.

Every route on this page except signup, login and refresh takes an access token from sign-in:

Authorization: Bearer <access_token>

Routes that read test or live data take ?mode=test for test data; without it they return live data.

Section Routes
Signing up and in signup, login, refresh, logout, me, password
Team /merchant/users
Stores /merchant/stores
Payment settings /merchant/settings, /merchant/tokens
Live mode /merchant/live
Webhook /merchant/webhook, /merchant/webhook/roll-secret, /merchant/webhooks/deliveries
Payments and dashboard /merchant/dashboard, /merchant/payments, events, trail
Audit log /merchant/audit
Balances and payouts balances, ledger, payout addresses, payouts
Support chat /merchant/support
Referral program /merchant/affiliate, /merchant/affiliate/payouts, /referrals/{code}/click
Real-time events socket.io /merchant

API keys are on the Authentication page.

Roles

Role Can
owner Everything: team, settings, live mode, the audit log, rolling the webhook secret, payouts, the referral program
developer API keys, stores, the webhook URL and test, redelivering webhooks
finance Payout addresses and payouts, the referral program
viewer Read payments, the dashboard, balances, the ledger, settlements and webhook deliveries; change nothing

Every role can read payments, the dashboard, balances, the ledger, settlements, payouts and webhook deliveries, and write in the support chat. A route your role may not use answers 403 {"error": "insufficient permissions"}.

Signing up and in

Open an account

POST /merchant/signup · no authentication · 5 a second per IP address (a limit of its own: sign-in and checkout have their own)

Field Type Required Description
merchant_name string yes Your business's name
email string yes The owner's email address, used to sign in
password string yes At least 8 characters
referral_code string no The code of the referral link that brought you (its ?ref=), in any case. An unknown or suspended code is ignored: it never stops the signup (Referral program)
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"}'

201 Created. It holds your first two API keys, shown only here, and signs you in (an access token, and the refresh cookie):

{
  "merchant": {
    "id": "3f8a1c27b94e05d6a2c71e90",
    "name": "Acme Store",
    "status": "active",
    "fee_bps": 100,
    "live_enabled": false,
    "underpaid_tolerance_bps": 50,
    "accept_other_assets": false,
    "settlement": "direct",
    "fees_paid_by_customer": true,
    "convert_into": "cheapest",
    "hide_below_minimum": false,
    "enabled_tokens": ["USDT_TRC20", "TRX", "ETH", "USDT_ERC20", "USDC_ERC20", "BNB", "USDT_BEP20", "USDC_BEP20", "BTC", "LTC", "DOGE", "XRP"],
    "created_at": "2026-10-10T08:57:25.902774252Z",
    "updated_at": "2026-10-10T08:57:25.902774252Z"
  },
  "user": {
    "id": "9b2e4c71d8a05f3e6c1a7b24",
    "merchant_id": "3f8a1c27b94e05d6a2c71e90",
    "email": "you@acme.example",
    "role": "owner",
    "created_at": "2026-10-10T08:57:25.902774252Z"
  },
  "api_keys": [
    {
      "api_key": { "id": "4f2a9c1e7b3d5a08c6e1d7b2", "merchant_id": "3f8a1c27b94e05d6a2c71e90", "livemode": false, "label": "default", "last4": "5a8f", "created_by": "9b2e4c71d8a05f3e6c1a7b24", "created_at": "2026-10-10T08:57:25.904339745Z" },
      "secret": "4f9c2a7e1b8d3f6a0c5e9b2d7a4f1c8e3b6d9a2f5c8e1b4d7a0f3c6e9b2d5a8f"
    },
    {
      "api_key": { "id": "8d3b6e2f1a4c9e07b5d2a8c4", "merchant_id": "3f8a1c27b94e05d6a2c71e90", "livemode": true, "label": "default", "last4": "e0f5", "created_by": "9b2e4c71d8a05f3e6c1a7b24", "created_at": "2026-10-10T08:57:25.905901832Z" },
      "secret": "ea2bc4d2247c7159eec631a51a086a4a6bbd156cedb9982ea99510c5a9f3e0f5"
    }
  ],
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJraW5k…",
  "expires_at": "2026-10-10T09:12:25.907004586Z",
  "role": "owner"
}
Status Message Cause
400 merchant_name is required · invalid email address · password must be at least 8 characters A field is missing or invalid
409 email already registered That email already has an account
429 rate limit exceeded Too many attempts from your IP address

The merchant object

Field Type Description
id string Your account id
name string Your business's name
status string active, or suspended by payitgo (no API calls, no sign-ins, no payments)
fee_bps integer payitgo's service fee in basis points (100 = 1%), agreed with your account
live_enabled boolean Whether live mode is on (Live mode)
underpaid_tolerance_bps integer How far short a payment may fall and still count as paid, in basis points
accept_other_assets boolean Whether a deposit in another accepted coin counts toward a payment
settlement string direct, custodial or scheduled
payout_schedule string Scheduled settlement: instant, daily or weekly (Mondays)
payout_scheduled_at string, optional Scheduled settlement: when your balance was last paid out
fees_paid_by_customer boolean Whether customers pay the fees on top of your price (on for a new account)
convert_into string Direct settlement: what a coin you have no payout address for is converted into: cheapest (the default), order, or your preferred payout coin (which coin)
hide_below_minimum boolean Whether a coin converted across networks is left out of a payment too small for the exchange's minimum, rather than the customer paying up to it (minimums)
enabled_tokens array of strings The coins you accept
created_at, updated_at string When the account was opened and last changed

Payment settings explains each setting.

The user object

Field Type Description
id string The team member's id
merchant_id string Your account
email string Their sign-in email
role string owner, developer, finance or viewer
created_at string When they were added
last_login_at string, optional Their last sign-in

Sign in, refresh, sign out

POST /merchant/login, POST /merchant/auth/refresh, POST /merchant/auth/logout and POST /merchant/auth/logout-all are described, with their responses and errors, on the Authentication page.

Who am I

GET /merchant/me · any role

curl -s https://api.payitgo.co/merchant/me -H "Authorization: Bearer $ACCESS_TOKEN"

200 OK: {"merchant": {…}, "user": {…}}, the merchant with its current settings and the signed-in user.

Change your password

POST /merchant/me/password · any role

Field Type Required Description
current_password string yes Your password now
new_password string yes At least 8 characters
curl -s https://api.payitgo.co/merchant/me/password \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"current_password": "a-long-password", "new_password": "an-even-longer-password"}'

200 OK: {"message": "password changed; sign in again"}. Every session of yours ends.

Status Message Cause
400 current_password and new_password are required A field is missing
400 password must be at least 8 characters new_password is too short
403 current password is incorrect Wrong current_password

Team

Invite colleagues with the role they need. Each person signs in with their own email and password. All four routes are owner only.

Method Path Body Response
GET /merchant/users 200 {"users": [user, …]}
POST /merchant/users email, password, role 201 {"user": user}
PATCH /merchant/users/{id} role 200 {"user": user}
DELETE /merchant/users/{id} 200 {"message": "user removed"}
curl -s https://api.payitgo.co/merchant/users \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"email": "dev@acme.example", "password": "a-first-password", "role": "developer"}'

201 Created:

{
  "user": {
    "id": "5e8a2c9d1f4b7036a2e9c1d8",
    "merchant_id": "3f8a1c27b94e05d6a2c71e90",
    "email": "dev@acme.example",
    "role": "developer",
    "created_at": "2026-10-10T08:59:43.427614842Z"
  }
}

The owner sets a first password and hands it over; the new member changes it with /merchant/me/password. Changing a role or removing someone ends their sessions (an access token already issued stays valid for up to 15 minutes).

Status Message Cause
400 invalid email address · password must be at least 8 characters Invalid new member
400 role must be one of owner, developer, finance, viewer Unknown role
404 not found No such member in your account
409 email already registered That email already belongs to someone
409 the account must keep at least one owner Demoting or removing the last owner

Stores

Selling from more than one shop (several websites, an app and a website, a few branches)? Create a store for each and give each its own API keys. Every payment a store's key creates carries the store's store_id, and the store's backend can send its notifications to its own URL with webhook_url. A merchant with one shop needs no stores.

Method Path Role Body Response
GET /merchant/stores any 200 {"stores": [store, …]}
POST /merchant/stores owner, developer name (1 to 100 characters) 201 {"store": store}
PATCH /merchant/stores/{id} owner, developer name 200 {"store": store}
curl -s https://api.payitgo.co/merchant/stores \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name": "Acme EU shop"}'

201 Created:

{
  "store": {
    "id": "6d2a9e4c1b7f30a58e2c4d91",
    "merchant_id": "3f8a1c27b94e05d6a2c71e90",
    "name": "Acme EU shop",
    "created_at": "2026-10-10T08:59:52.920128021Z",
    "updated_at": "2026-10-10T08:59:52.920128021Z"
  }
}

Then give it a key: POST /merchant/api-keys?mode=test {"label": "eu-shop", "store_id": "6d2a9e4c1b7f30a58e2c4d91"} (Create a key). Errors: 400 name is required (up to 100 characters), 404 not found.

Each store has its own order ids. An order_id is unique within a store: a store's key creates and finds its own store's orders only, and a key with no store those made with no store's key. Two shops on one account can both number their orders from 1: give each the key of its own store, and their order_ids never meet (safe retries).

Payment settings

PATCH /merchant/settings · owner

Send only the settings you change. A payment keeps the settings in force when it was created.

Field Type Default Description
underpaid_tolerance_bps integer 50 (0.5%) How far short a payment may fall and still count as paid, 0 to 2000 (20%) (wrong amount)
accept_other_assets boolean false Whether a deposit in another accepted coin counts toward the payment at its dollar value (wrong asset)
settlement string direct direct (your payout address for the coin, or converted into one of your payout coins when you have none ready for it; your balance when it cannot be), custodial (your balance, in USDT on Tron) or scheduled (your balance, paid out to you on a schedule) (where the money goes)
payout_schedule string daily Scheduled settlement: instant (a payout as soon as each payment is credited), daily, or weekly (on Mondays)
fees_paid_by_customer boolean true Whether the customer pays the service and network fees on top of your price; off, they come out of what you receive (passing fees on)
convert_into string cheapest Direct settlement: what a coin you have no payout address for is converted into. cheapest: whichever of your payout coins costs the least at each payment's amount; order: the first of a fixed order; a coin (BTC, USDT_BEP20…, any coin you have a payout address for): your preferred payout coin, used whenever a payment can be converted into it (which coin)
hide_below_minimum boolean false Direct settlement, with the fees passed on: false offers a coin converted across networks even for a payment too small for the exchange's minimum, the customer paying up to it (you receive the extra, extra_usd); true offers it only from the price that reaches the minimum (minimums). When you pay the fees yourself, such a coin is always offered only from that price
enabled_tokens array of strings every token The coins you accept, at least one (accepted tokens)
curl -s -X PATCH https://api.payitgo.co/merchant/settings \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"underpaid_tolerance_bps": 100, "enabled_tokens": ["USDT_TRC20", "USDT_BEP20", "USDC_BEP20", "TRX"]}'

200 OK: {"merchant": {…}}, the merchant object with the new settings.

Status Message Cause
400 nothing to update An empty body
400 underpaid_tolerance_bps must be between 0 and 2000 Out of range
400 settlement must be "custodial", "direct" or "scheduled" Unknown mode
400 unsupported token: SHIB An unknown entry in enabled_tokens
400 enabled_tokens must enable at least one token An empty list
403 insufficient permissions Not the owner

Accepted tokens

enabled_tokens is the list of coins you accept, such as ["USDT_TRC20", "USDT_BEP20", "USDC_BEP20"]. Only those are offered to your customers, listed by GET /v1/tokens, and allowed in a payment's token or tokens (others get 400 not enabled for your account: …). A deposit in a coin you have not enabled still reaches you, but never counts toward the payment. Enabling every token also accepts tokens payitgo adds later.

GET /merchant/tokens?mode=test · any role

Every supported token, marked enabled or not, with where its payments go and its fee and minimum right now: what a settings screen shows. Without mode=test, the live ones.

curl -s 'https://api.payitgo.co/merchant/tokens' -H "Authorization: Bearer $ACCESS_TOKEN"

200 OK (two of the twelve tokens shown; direct settlement, with a live payout address for USDT on Tron only, so BTC is converted into it):

{
  "tokens": [
    { "token": "USDT_TRC20", "chain": "tron", "min_usd": "6.44", "network_fee_usd": "3.22", "fee_note": "Final in about a minute.", "enabled": true, "settles_to": "wallet" },
    { "token": "BTC", "chain": "bitcoin", "min_usd": "13.2", "network_fee_usd": "6.6", "network_fee_bps": 180, "fee_note": "Final in about 30 minutes (3 confirmations).", "enabled": false, "settles_to": "wallet", "paid_out_in": "USDT_TRC20" }
  ]
}

Each entry is a TokenInfo with three more fields:

Field Type Description
enabled boolean Whether you accept it
settles_to string wallet (direct settlement, with a payout address for it, in use or still in its 24 hours, or one in use for the coin it is converted into) or balance (custody or scheduled settlement, or a coin that converts into none of your payout coins, such as any coin without a test payout address in test mode)
paid_out_in string, optional The payout coin a payment in it is converted into on its way to your wallet, when you have no payout address for it (coins you have no payout address for). Absent when it is paid out as it is (an address still in its 24 hours included: its payments wait for it), or kept in your balance

Live mode

POST /merchant/live · owner

Field Type Required Description
enabled boolean yes true turns live mode on, false off
curl -s https://api.payitgo.co/merchant/live \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"enabled": true}'

200 OK: {"merchant": {…, "live_enabled": true, …}}. From now on the live key works. Turning it off stops new live payments; open ones still complete. 400 {"error": "enabled (true or false) is required"} when enabled is missing.

Webhook

Each mode has one webhook: a URL and a signing secret (Webhooks). Your server manages the URL with its API key (/v1/webhook); people do it here, choosing the mode with ?mode=test (default live). Rolling the secret is only possible here.

Method Path Role Response
GET /merchant/webhook?mode=test owner, developer 200 {"webhook": Webhook}
PUT /merchant/webhook?mode=test owner, developer 200 {"webhook": Webhook}
DELETE /merchant/webhook?mode=test owner, developer 200 {"webhook": Webhook} with "url": null
POST /merchant/webhook/test?mode=test owner, developer 202 {"delivery": Delivery}
POST /merchant/webhook/roll-secret?mode=test owner 200 {"webhook": Webhook} with a new secret
GET /merchant/webhooks/deliveries?mode=test any 200 {"deliveries": [Delivery, …]}
GET /merchant/webhooks/deliveries/{id} any 200 {"delivery": Delivery}
POST /merchant/webhooks/deliveries/{id}/redeliver owner, developer 202 {"message": "redelivery queued"}

The first five behave exactly like their /v1/webhook counterparts, with the same body ({"url": "https://…"} for PUT) and the same errors. The objects are described there: Webhook and Delivery.

Roll the signing secret

POST /merchant/webhook/roll-secret?mode=test · owner

Replaces the mode's signing secret. Every notification sent from now on, retries of older deliveries included, is signed with the new secret. The old one stops working at once, so have your server ready to accept the new one (for example by trying both for a few minutes) before you roll it.

curl -s -X POST 'https://api.payitgo.co/merchant/webhook/roll-secret?mode=test' \
  -H "Authorization: Bearer $ACCESS_TOKEN"

200 OK:

{
  "webhook": {
    "livemode": false,
    "url": "https://acme.example/payitgo/ipn",
    "secret": "b81c…07fa",
    "created_at": "2026-10-10T08:58:44.128Z",
    "updated_at": "2026-10-10T08:59:29.906Z"
  }
}

A developer gets 403 {"error": "insufficient permissions"}.

Delivery history

GET /merchant/webhooks/deliveries · any role

Every notification payitgo sent you (or tried to), newest first, with every attempt.

Query parameter Default Description
mode live test for test deliveries
status all pending, sending, delivered, failed or resolved
limit 50 1 to 200
curl -s 'https://api.payitgo.co/merchant/webhooks/deliveries?mode=test&status=failed&limit=20' \
  -H "Authorization: Bearer $ACCESS_TOKEN"

200 OK: {"deliveries": [Delivery, …]} (no paging beyond limit). One delivery, with its full history: GET /merchant/webhooks/deliveries/{id}, 200 {"delivery": Delivery} or 404 {"error": "not found"}. An example is under Delivery history and redelivery.

Redeliver

POST /merchant/webhooks/deliveries/{id}/redeliver · owner, developer

Sends a delivery again from the start (attempts back to 0, seven more tries), whatever its status: after you fixed your handler, or to replay an event. It is posted to the account's webhook URL as it is now (or, for a payment's own webhook_url, to that URL), signed with the current secret.

curl -s -X POST https://api.payitgo.co/merchant/webhooks/deliveries/8e3c1a7f5d9b2e4c6a0f1d3b/redeliver \
  -H "Authorization: Bearer $ACCESS_TOKEN"

202 Accepted:

{"message": "redelivery queued"}

An unknown id answers 404 {"error": "not found"}.

Payments and dashboard

Method Path Role Response
GET /merchant/dashboard?mode=test any Today's figures, and confirmed payments over 7 days, 30 days and all time
GET /merchant/payments?mode=test any Payments, as GET /v1/payments
GET /merchant/payments/{id} any One payment, by payment id, test or live
GET /merchant/payments/{id}/events any The payment's event timeline
GET /merchant/payments/{id}/trail any Everything about one payment, for a dispute
POST /merchant/payments/{id}/simulate owner, developer, finance Plays an outcome on a test payment (below)

/merchant/payments takes the same filters as GET /v1/payments (status, token, chain, store_id, limit, offset) plus mode. An unknown payment answers 404 {"error": "payment not found"}.

Every list on this page answers the limit and offset it applied. A limit outside its range (1 to 200; 1 to 500 for the ledger and the audit log) is replaced by the default (50; 100 for the audit log and a payment's events), and a negative offset by 0.

Dashboard

curl -s 'https://api.payitgo.co/merchant/dashboard?mode=test' -H "Authorization: Bearer $ACCESS_TOKEN"

200 OK:

{
  "livemode": false,
  "confirmed_today": 0,
  "volume_usd_today": "0.00",
  "pending": 4,
  "expired_today": 0,
  "failed_webhooks": 0,
  "by_chain": {},
  "periods": {
    "today":        {"confirmed": 0, "volume_usd": "0.00",    "by_chain": {}},
    "last_7_days":  {"confirmed": 2, "volume_usd": "15.50",   "by_chain": {"tron": "10.00", "bsc": "5.50"}},
    "last_30_days": {"confirmed": 3, "volume_usd": "115.50",  "by_chain": {"tron": "110.00", "bsc": "5.50"}},
    "all_time":     {"confirmed": 4, "volume_usd": "1115.50", "by_chain": {"tron": "110.00", "bsc": "5.50", "bitcoin": "1000.00"}}
  },
  "generated_at": "2026-10-10T09:00:04.863003436Z"
}
Field Description
livemode false for the test-mode figures (?mode=test)
confirmed_today Payments confirmed since midnight UTC
volume_usd_today Their total in dollars
pending Open payments now: pending, partially_paid and detecting
expired_today Expired payments (status: "expired") whose deadline passed since midnight UTC
failed_webhooks Deliveries that gave up and wait for a redelivery
by_chain The confirmed volume since midnight UTC per network, in dollars (the same window as volume_usd_today)
periods Confirmed payments over four spans: today, last_7_days, last_30_days and all_time (below)
generated_at When these figures were computed

Each of periods has:

Field Description
confirmed How many payments confirmed in the span
volume_usd Their total in dollars, each at its own dollar value (usd_amount), 2 decimals
by_chain That total per network, in dollars. Only networks with a confirmed payment in the span appear

The spans are calendar days in UTC: today since midnight UTC, last_7_days today and the 6 days before it, last_30_days today and the 29 before it, all_time every payment ever confirmed. Only confirmed payments count, of the mode asked (?mode=test for test mode). periods.today is the same as confirmed_today, volume_usd_today and by_chain.

Event timeline

GET /merchant/payments/{id}/events?limit=100

Every event the payment raised, oldest first: the same events your webhook receives, kept even when no URL was set.

{
  "events": [
    {
      "id": "9d4b2f7e1c6a3e08b5d2f1c4",
      "merchant_id": "3f8a1c27b94e05d6a2c71e90",
      "payment_id": "2d9b6e14a7c3f0851e4b9a6c",
      "type": "payment.expired",
      "data": { "id": "2d9b6e14a7c3f0851e4b9a6c", "status": "expired", "…": "…" },
      "created_at": "2026-10-10T09:15:04.117Z"
    }
  ]
}

type is the event type, and data is exactly the data of the notification: the payment (or settlement) as it was at that moment, with the same field names.

A payment's trail

GET /merchant/payments/{id}/trail gathers everything payitgo recorded about a payment, for when a customer says they paid, or paid twice, or you need to show where the money went. The answer is {"trail": {…}} with:

Field Description
payment The payment
deposits Every transfer to it, with its sender, block and time
unmatched_deposits Transfers made to its address (or destination tag) after it closed that no payment took
events Its event timeline, as above
webhook_deliveries Every notification sent about it, with every attempt: when, where, and your server's answer
settlements Each deposit's {"settlement": …, "transfers": […]}: the settlement and every transaction that sent you money (and any it replaced)
ledger Every ledger entry it made
audit Who did what about it: the API key that created it, the customer's coin choice (with their IP), payitgo's actions
generated_at When the trail was gathered

Simulate a test payment

POST /merchant/payments/{id}/simulate · owner, developer, finance

The dashboard's way to simulate an outcome on a test payment, from its page in test mode: the same as POST /v1/payments/{id}/simulate, with an access token instead of an API key. {id} is the payment id.

curl -s -X POST https://api.payitgo.co/merchant/payments/c41e7a9d2b05f3e8a6d17c90/simulate \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"outcome": "confirmed"}'

200 OK with the payment, with "simulation": {"outcome": "confirmed", "state": "running"}. A live payment answers 400 only test payments can be simulated: use a test key; the outcomes, rules and every error are in Test mode. A viewer gets 403 {"error": "insufficient permissions"}.

Audit log

GET /merchant/audit · owner

The account's record of who did what, newest first and never changed: sign-ins and failed sign-ins, team and role changes, API keys, the webhook URL and secret, payout addresses (before and after), settings, payouts, payments created, your referrals (referral.created, referral.voided), and payitgo's actions on the account.

Query parameter Description
action An exact action (webhook.url_set) or a prefix ending in a dot (webhook.)
subject The id of the record acted on
payment_id Entries about one payment
actor_id Entries by one team member or API key
from, to Dates, 2026-10-01
limit, offset Paging; default 100, up to 500
curl -s 'https://api.payitgo.co/merchant/audit?action=webhook.&limit=2' -H "Authorization: Bearer $ACCESS_TOKEN"

200 OK:

{
  "entries": [
    {
      "id": "8b2d4f6a1c3e5a7c9e0b2d4f",
      "at": "2026-10-10T09:00:04.734Z",
      "action": "webhook.url_set",
      "actor": {
        "kind": "api_key",
        "id": "4f2a9c1e7b3d5a08c6e1d7b2",
        "merchant_id": "3f8a1c27b94e05d6a2c71e90",
        "ip": "203.0.113.24",
        "user_agent": "curl/8.18.0",
        "request_id": "kT4pWq9ZrX2mNc7bLv5sHd8fJg3yAe6R"
      },
      "merchant_id": "3f8a1c27b94e05d6a2c71e90",
      "subject": "3f8a1c27b94e05d6a2c71e90:test",
      "before": { "url": null },
      "after": { "url": "https://acme.example/payitgo/ipn" }
    },
    {
      "id": "1e3c5a7f9b2d4f6a8c0e1b3d",
      "at": "2026-10-10T08:59:29.907Z",
      "action": "webhook.secret_rolled",
      "actor": {
        "kind": "merchant",
        "id": "9b2e4c71d8a05f3e6c1a7b24",
        "email": "you@acme.example",
        "role": "owner",
        "merchant_id": "3f8a1c27b94e05d6a2c71e90",
        "ip": "203.0.113.24",
        "user_agent": "Mozilla/5.0 …",
        "request_id": "Qm7sVb2nXc9kLp4wZr6tHy3dFg8jAe5N"
      },
      "merchant_id": "3f8a1c27b94e05d6a2c71e90",
      "subject": "3f8a1c27b94e05d6a2c71e90:test",
      "note": "signing secret replaced"
    }
  ],
  "total": 6,
  "limit": 2,
  "offset": 0
}
Field Description
action What happened, <what>.<verb>: webhook.url_set, webhook.url_removed, webhook.secret_rolled, payout_address.updated, payment.created…
actor.kind Who: merchant (a team member), api_key, customer (a payer on the checkout), admin (payitgo's team, without the person's name), system (payitgo itself) or anonymous (before sign-in)
actor.ip, actor.user_agent, actor.request_id Where the request came from, and its X-Request-Id
subject The record acted on (the webhook is <merchant id>:test or :live)
before, after The values before and after. Secrets are never recorded
note A short explanation

A bad date answers 400 {"error": "from must be a date: 2006-01-02"}.

Balances and payouts

Where a confirmed payment's money goes depends on your settlement: with direct settlement it is sent to your payout address; in custody it is credited to your balance, in USDT on Tron, and you request payouts. With direct settlement, what payitgo could not send you stays in your balance, and a USDT balance is paid out to you automatically once you have a USDT address on BNB Smart Chain or Ethereum. Test payments credit your test balance, which is never paid out. Payouts are live only.

Referral commissions are not part of this balance: they build up in your referral balance, which is paid out on its own.

Method Path Role
GET /merchant/balances?mode=test any
GET /merchant/ledger?mode=test · /merchant/ledger.csv any
GET /merchant/settlements?mode=test any
GET POST PATCH DELETE /merchant/payout-addresses read: any; change: owner, finance
GET POST /merchant/payouts read: any; request: owner, finance
GET /merchant/payouts/quote?address_id=&amount= owner, finance
POST /merchant/payouts/{id}/cancel owner, finance

Balances

GET /merchant/balances?mode=test

curl -s https://api.payitgo.co/merchant/balances -H "Authorization: Bearer $ACCESS_TOKEN"

200 OK:

{
  "balances": [
    { "token": "USDT_TRC20", "chain": "tron", "available": "1043.27", "pending_payout": "250" }
  ]
}
Field Type Description
token, chain string The coin
available decimal string What you can request a payout of now
pending_payout decimal string What is held for payouts not sent yet, on the balance each one draws on: a payout in USDT on another network counts here (its from_token)

An account with nothing credited yet gets {"balances": []}. Referral commissions are not counted here.

Ledger

GET /merchant/ledger?mode=test&token=USDT_TRC20&limit=50&offset=0

Your statement: every movement on your balance, newest first. Referral commissions and referral payouts are not in it.

{
  "entries": [
    { "id": "5f446eb968ee2af08be79772", "merchant_id": "3f8a1c27b94e05d6a2c71e90", "type": "network_fee", "token": "USDT_TRC20", "chain": "tron", "amount": "-3.212009", "usd_amount": "-3.212009", "reference": "8f3a2c1e9b7d4a6f05e1c2d3:c19240…51bd", "created_at": "2026-10-10T09:04:03.81Z" },
    { "id": "c921ff34b8f23cd4346464ae", "merchant_id": "3f8a1c27b94e05d6a2c71e90", "type": "fee", "token": "USDT_TRC20", "chain": "tron", "amount": "-0.499", "usd_amount": "-0.499", "reference": "8f3a2c1e9b7d4a6f05e1c2d3:c19240…51bd", "created_at": "2026-10-10T09:04:03.81Z" },
    { "id": "23eb41673da1f63b49577d2b", "merchant_id": "3f8a1c27b94e05d6a2c71e90", "type": "payment_credit", "token": "USDT_TRC20", "chain": "tron", "amount": "49.9", "usd_amount": "49.9", "reference": "8f3a2c1e9b7d4a6f05e1c2d3:c19240…51bd", "created_at": "2026-10-10T09:04:03.81Z" }
  ],
  "total": 3,
  "limit": 50,
  "offset": 0
}

The ledger entry object

Field Type Description
id string The entry's id
merchant_id string Your account
type string What it is (below)
token, chain string The coin
amount decimal string Signed: credits positive, debits negative
usd_amount decimal string Its dollar value at the time
reference string <payment id>:<transaction id> for a deposit, or the payout id
note string, optional Details, such as what a converted deposit was
created_at string When it was recorded
type Meaning
payment_credit A final deposit to one of your payments
fee payitgo's service fee on that deposit
network_fee The network fee of moving that deposit
conversion_fee The fee for converting the deposit into another coin
conversion_out · conversion_in The deposit leaving your balance to be converted (noted converting into USDT_TRC20 while an exchange converts it), and what the conversion bought coming back
settlement Direct settlement: your share leaving for your own wallet (converted into USDT_TRC20 and sent to your wallet when it was converted across networks on the way)
settlement_return A direct settlement that could not be sent, kept in your balance instead
payout_hold · payout_release A payout request holding the amount, and a rejected or cancelled payout giving it back, in the balance the payout draws on (its from_token when it has one)
adjustment A correction made by payitgo's team

Your share of payitgo's fee on a referred merchant's payment (an affiliate_commission), and the payouts of it, are neither listed here nor counted in GET /merchant/balances. They make your referral balance: its history is GET /merchant/affiliate/commissions and GET /merchant/affiliate/payouts.

GET /merchant/ledger.csv takes the same mode and token and returns the whole statement, newest first, as a spreadsheet, with the columns id, created_at, type, token, chain, amount, usd_amount, reference, note. Like every dashboard route it needs the Authorization header (an access token is never accepted in the URL), so a download button fetches it and saves the response.

Payout addresses

Your wallets: where payouts go, and, with direct settlement, where each coin's payments are sent. Add one for each coin you accept: that coin's payments are then sent to you as they arrive, with no conversion, at the lowest fee for your customers; a coin without one is converted into one of your payout coins first, and costs them more (why). Test addresses (?mode=test) receive test settlements, on paper: nothing is ever sent to them. A Bitcoin or Litecoin test address may be in the mainnet or the testnet format (tb1…, m…, n…, tltc1…). Adding an address or changing one asks for your password again.

Method Path Role Body Response
GET /merchant/payout-addresses?mode=test any 200 {"addresses": [address, …]}
POST /merchant/payout-addresses?mode=test owner, finance token, address, tag, label, password 201 {"address": address}
PATCH /merchant/payout-addresses/{id} owner, finance any of address, tag, label, default, and password when address or tag change 200 {"address": address}
DELETE /merchant/payout-addresses/{id} owner, finance 200 {"message": "address deleted"}
Field Type Required Description
token string yes (POST) The coin this address receives, such as USDT_TRC20. It cannot change later
address string yes (POST) The wallet address, checked for the token's network
tag integer no XRP only: the destination tag your exchange asks for
label string no Your name for it
default boolean no (PATCH) true makes it the address direct settlement pays for its coin
password string when adding, or changing address or tag Your sign-in password
curl -s https://api.payitgo.co/merchant/payout-addresses \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"token": "USDT_TRC20", "address": "TWd4Kp9mR2xH7sN3vB6cJ8fL5qY1tZgEaU", "label": "Treasury", "password": "a-long-password"}'

201 Created:

{
  "address": {
    "id": "3e7a1c9f5b2d4e8a6c0f2b1d",
    "merchant_id": "3f8a1c27b94e05d6a2c71e90",
    "livemode": true,
    "token": "USDT_TRC20",
    "chain": "tron",
    "address": "TWd4Kp9mR2xH7sN3vB6cJ8fL5qY1tZgEaU",
    "label": "Treasury",
    "default": true,
    "active_at": "2026-10-11T09:00:29.688370009Z",
    "created_by": "9b2e4c71d8a05f3e6c1a7b24",
    "created_at": "2026-10-10T09:00:29.688370009Z"
  }
}
Field Description
id The address's id
livemode false for a test address
token, chain, address, tag, label As you set them
default Whether direct settlement pays this coin here. The first address of a coin is its default
active_at When direct settlement starts using it. Your first live address of a coin: at once, as there was nowhere else for those payments to go. Any address that could redirect payments (another address of a coin, a changed address or tag, one added after the coin's last address was deleted): 24 hours later, so whoever might take over your account cannot redirect your payments at once; payments in its coin wait for it meanwhile, and are then sent to it as they are. Test addresses are active at once
created_by, created_at Who added it, and when
Status Message Cause
400 unsupported token in live mode Not a token of that mode
400 address is not a valid tron address The address is not valid for the token's network
400 only XRP addresses take a destination tag tag on a non-XRP address
400 nothing to update An empty PATCH
403 password is incorrect Wrong or missing password
404 not found No such address

Payouts

Ask for your balance to be sent to one of your live payout addresses. payitgo reviews each payout you request, then sends it: one in USDT on BNB Smart Chain or Ethereum by itself, usually within minutes of approving it, any other by payitgo's team. A USDT balance can be paid to your USDT address on any network (below). Referral commissions are not paid out here: ask for a referral payout.

Quote a payout

GET /merchant/payouts/quote?address_id=…&amount=… · owner, finance

What a payout to one of your live addresses would be, before you request it; nothing is held. amount is optional: without it you get the network fee and the minimum payout only. The answer is {"quote": {...}} with address_id, token, chain, network_fee and network_fee_usd (what sending it costs now), min_payout and min_payout_usd, and for an amount amount, amount_usd, send_amount and send_amount_usd (what reaches the address) and from_token, from_chain (the balance it would draw on, when not the address's own coin's). It answers 400 for what a request would refuse: under the minimum, an amount the fee eats, too little balance. Fees move: the request's own answer has the final fee.

POST /merchant/payouts · owner, finance

Field Type Required Description
address_id string yes A live payout address's id
amount number or decimal string yes How much of the address's coin to pay out, at least $10 worth. The network fee of sending it comes out of this amount. Taken from your balance of that coin; for a USDT address, from another of your USDT balances when that one has too little
curl -s https://api.payitgo.co/merchant/payouts \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"address_id": "3e7a1c9f5b2d4e8a6c0f2b1d", "amount": "250"}'

201 Created. The amount is held from your balance at once:

{
  "payout": {
    "id": "7e2c9a4f1b3d8e6a05c2f1d9",
    "merchant_id": "3f8a1c27b94e05d6a2c71e90",
    "token": "USDT_TRC20",
    "chain": "tron",
    "amount": "250",
    "address_id": "3e7a1c9f5b2d4e8a6c0f2b1d",
    "address": "TWd4Kp9mR2xH7sN3vB6cJ8fL5qY1tZgEaU",
    "network_fee": "3.21",
    "network_fee_usd": "3.21",
    "send_amount": "246.79",
    "usd_rate": "1",
    "amount_usd": "250",
    "send_amount_usd": "246.79",
    "status": "requested",
    "requested_by": "9b2e4c71d8a05f3e6c1a7b24",
    "created_at": "2026-10-10T10:00:00.31Z",
    "updated_at": "2026-10-10T10:00:00.31Z"
  }
}

Paying USDT out on another network

USDT is worth $1 on every network, so a USDT balance pays a payout to any of your USDT payout addresses: USDT_TRC20, USDT_BEP20 or USDT_ERC20. Your custody balance, held in USDT on Tron, can go to your USDT address on BNB Smart Chain, where sending it costs about a cent instead of a few dollars on Tron.

The request is the same (address_id, amount), and payitgo picks the balance: the address's own coin when it has enough, otherwise another USDT balance that has, USDT on Tron first. One balance pays the whole payout: it is never split. The payout says which in from_token and from_chain, while token and chain are the coin and network it is paid in. The minimum payout ($10) applies as ever, the network fee is that of the network it is paid on, and the amount is held from the balance drawn on (its pending_payout, and the ledger's payout_hold).

The same 250 USDT from your Tron balance, to your USDT address on BNB Smart Chain:

{
  "payout": {
    "id": "5b9d1f3a7c2e4b6d8f0a1c3e",
    "merchant_id": "3f8a1c27b94e05d6a2c71e90",
    "token": "USDT_BEP20",
    "chain": "bsc",
    "amount": "250",
    "from_token": "USDT_TRC20",
    "from_chain": "tron",
    "address_id": "8c4e2a6f1b3d5e7a9c0b2d4f",
    "address": "0x4b7e2d9a1c6f3e8b5a0d7c2e9f4a1b6d3c8e5f2a",
    "network_fee": "0.00412345",
    "network_fee_usd": "0.01",
    "send_amount": "249.99587655",
    "usd_rate": "1",
    "amount_usd": "250",
    "send_amount_usd": "249.99",
    "status": "requested",
    "requested_by": "9b2e4c71d8a05f3e6c1a7b24",
    "created_at": "2026-10-10T10:00:00.31Z",
    "updated_at": "2026-10-10T10:00:00.31Z"
  }
}

Automatic payouts

With direct settlement, what payitgo could not send you stays in your balance (when). payitgo pays a USDT balance out to you by itself, as one payout of the whole available: to your USDT address on BNB Smart Chain that direct settlement uses (past its active_at; your default one first), else on Ethereum. It looks every 10 minutes, and pays as soon as it can send the payout. Such a payout has requested_by: "auto", is approved at once and sent by payitgo for the network's fee (about a cent on BNB Smart Chain), with the usual payout.approved and payout.sent webhooks.

Nothing is paid out automatically under the minimum payout ($10), to an address still in its 24 hours, or without a USDT address on BNB Smart Chain or Ethereum: request a payout of it as usual (to your USDT address on Tron, say). Custody waits for your requests, and scheduled settlement keeps its schedule.

The payout object

Field Type Description
id string The payout's id
token, chain string The coin paid out, and its network
from_token, from_chain string, optional The balance it draws on, when that is not token's: a USDT balance paid out as USDT on another network (USDT_TRC20, tron for your custody balance paid to USDT_BEP20). Absent when it draws on its own coin
amount decimal string Taken from your balance (from_token's when present)
address_id, address, tag string Where it goes, fixed when you requested it (changing the payout address later does not move it)
network_fee, network_fee_usd decimal string The network fee of sending it, quoted at the request, in the coin and in dollars
send_amount decimal string What reaches your address: amount less network_fee
usd_rate decimal string The coin's dollar price when you requested the payout, to 8 decimals ("1" for USDT)
amount_usd, send_amount_usd decimal string amount in dollars at usd_rate, to the cent, and what network_fee_usd leaves of it, so the three add up
status string requested → approved → sent; or rejected (by payitgo) or canceled (by you), which return the amount to your balance
tx_id string, optional The transaction, once sent
review_note string, optional payitgo's note, for example why it was rejected
requested_by string Who asked for it: a user's id, scheduler for a scheduled settlement's payout, or auto for a balance paid out automatically
approved_at, rejected_at, canceled_at, sent_at string, optional When each step happened
canceled_by string, optional Who cancelled it
created_at, updated_at string When it was requested and last changed

Each step payitgo takes raises a webhook to your live account webhook URL, with this object as data: payout.approved, payout.rejected, payout.sent (with tx_id).

Method Path Response
GET /merchant/payouts?status=&limit=&offset= 200 {"payouts": [payout, …], "total": n, "limit": 50, "offset": 0}
GET /merchant/payouts/{id} 200 {"payout": payout}
POST /merchant/payouts/{id}/cancel 200 {"payout": payout} with "status": "canceled", while it is still requested
Status Message Cause
400 amount must be > 0 Zero or negative
400 unknown payout address (test balances are not paid out) Not one of your live addresses
400 the minimum payout is $10.00 (about 10 USDT_TRC20) Under $10 worth
400 amount must be more than the network fee of 3.21 USDT_TRC20 Nothing would be left to send
400 insufficient available balance More than available; for a USDT address, more than any one of your USDT balances has
404 not found No such payout
409 payout is not in a state that allows this Cancelling a payout that is no longer requested

Support chat

Your account has one chat with payitgo's support, shared by the whole team: every role can read it and write in it, and everyone sees every message, whoever on the team wrote it. A message is text, up to 5000 characters (spaces at either end are trimmed). You send it over REST, and every open dashboard receives it live, support's replies included: there is nothing to poll.

Method Path Role Body Response
GET /merchant/support?before=&limit= any 200 {"messages": [message, …], "more": false, "chat": chat}
GET /merchant/support/unread any 200 {"unread": 1}
POST /merchant/support/messages any text 201 {"message": message}
POST /merchant/support/read any 200 {"chat": chat}
PATCH /merchant/support/messages/{id} its author text 200 {"message": message}
DELETE /merchant/support/messages/{id} its author 200 {"message": message}

Sending and editing are limited to one message a second per person, in bursts of up to 5.

curl -s https://api.payitgo.co/merchant/support/messages \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"text": "Hello, our webhook deliveries have been failing since this morning. Is anything wrong on your side?"}'

201 Created:

{
  "message": {
    "id": "4c9e1a7f3b2d8e6a05f1c2d9",
    "merchant_id": "3f8a1c27b94e05d6a2c71e90",
    "author": "merchant",
    "author_id": "5e8a2c9d1f4b7036a2e9c1d8",
    "author_name": "dev",
    "text": "Hello, our webhook deliveries have been failing since this morning. Is anything wrong on your side?",
    "created_at": "2026-10-14T09:12:03.118Z"
  }
}

Reading the chat

GET /merchant/support?before=&limit= · any role

The latest messages, oldest first. more is true when older ones remain: ask for them with before=<the first message's id>. limit is 1 to 100 (default 50).

{
  "messages": [
    { "id": "4c9e1a7f3b2d8e6a05f1c2d9", "merchant_id": "3f8a1c27b94e05d6a2c71e90", "author": "merchant", "author_id": "5e8a2c9d1f4b7036a2e9c1d8", "author_name": "dev", "text": "Hello, our webhook deliveries have been failing since this morning. Is anything wrong on your side?", "created_at": "2026-10-14T09:12:03.118Z" },
    { "id": "8b2d6f0a4c1e9a3d7f5b2c6e", "merchant_id": "3f8a1c27b94e05d6a2c71e90", "author": "support", "author_name": "Maya", "text": "Hi! Nothing on ours: your server answers 502. Deliveries retry on their own, and you can redeliver the failed ones once it is fixed.", "created_at": "2026-10-14T09:15:40.502Z" }
  ],
  "more": false,
  "chat": { "agent_name": "Maya", "waiting": false, "support_read_at": "2026-10-14T09:15:40.502Z", "unread": 1, "messages": 2 }
}

Reading marks nothing read, so a dashboard can load the chat, or show a badge with GET /merchant/support/unread ({"unread": 1}: support's messages your team has not read), without support seeing it read. When someone has the chat open on screen, call POST /merchant/support/read: support sees your team read it (merchant_read_at), and unread goes back to 0 for the whole team. Before its first message the chat is empty: "messages": [], with "waiting": false and "unread": 0.

The message object

Field Type Description
id string The message's id
merchant_id string Your account
author string merchant (someone on your team) or support
author_id string, optional The team member who wrote it (their id); absent on support's messages
author_name string Their name (for a member without one, their email address before the @), or the support agent's
text string The message; "" once deleted
created_at string When it was sent
edited_at string, optional When it was last edited
deleted_at string, optional When it was deleted

The chat object

Field Type Description
agent_name string, optional The support agent who has your chat
waiting boolean true from your team's message until support replies
merchant_read_at string, optional When your team last marked the chat read
support_read_at string, optional When support last read it: your messages sent before then have been seen
unread integer Support's messages your team has not read
messages integer How many messages the chat holds, deleted ones included

A support agent takes your chat when you write (agent_name) and answers it. Support sees every message with its writer's name, your account's name, when your team last read the chat, and when someone on your team is typing.

When support replies while nobody on your team has the dashboard open (no socket connected), the team is told: a notification in the support category, in the bell of every role and by email to those who chose it (on by default), with the reply's first 300 characters. At most one every 10 minutes.

Editing and deleting

PATCH /merchant/support/messages/{id} {"text": "…"} and DELETE /merchant/support/messages/{id} · the message's author

Each person edits and deletes only their own messages. An edit sets edited_at. A deleted message stays in the chat with "text": "" and deleted_at: show it as deleted. Deleting it again changes nothing, and it can no longer be edited. payitgo keeps what a message said before each edit and before it was deleted, for disputes; nobody is shown it.

Status Message Cause
400 text is required · text is too long (5012 characters; up to 5000) An empty message, or over 5000 characters
403 only its author can change a message Editing or deleting someone else's message
404 message not found No such message in your chat (also a before that is none)
409 this message was deleted Editing a deleted message
429 rate limit exceeded More than one message a second, past a burst of 5

Live chat updates

Every team member's dashboard socket receives:

Event Payload
support:message A new message: a team member's (the sender's own included: upsert it by id) or support's
support:message:update A message edited or deleted: the whole message, to replace the one shown
support:read {"side", "read_at", "unread"}. side support: support read the chat up to read_at. merchant: a team member marked it read, and the badge is back to 0
support:typing {"side": "support", "name", "typing"}: someone at support started (true) or stopped typing

The dashboard tells support someone is typing with an event of its own, support:typing: true (or {"typing": true}) while someone types in the chat box, false when they stop or send. Support sees the person's name while they type. It is acknowledged with {"ok": true}. A true sent less than a second after the same person's last true is not passed on again (support is already shown them typing), so there is no need to send it more than once a second; false always goes through.

socket.on("support:message", (m) => upsertMessage(m));
socket.on("support:message:update", (m) => upsertMessage(m));
socket.on("support:read", (r) => (r.side === "support" ? showSeen(r.read_at) : setBadge(0)));
socket.on("support:typing", (t) => showTyping(t.typing ? `${t.name} is typing…` : null));

// while someone types (at most once a second), and when they stop or send
await socket.emitWithAck("support:typing", true);  // {"ok": true}
await socket.emitWithAck("support:typing", false);

Referral program

Every payitgo account can refer other businesses and earn from what they process. Share your referral link: a business that opens an account through it is your referral, and each of its live payments earns you a share of payitgo's service fee on it:

Only the service fee is shared, never the network fee or a conversion fee. payitgo may agree other terms with you (a rate of your own, a longer time, or for as long as your referrals use payitgo): GET /merchant/affiliate always shows the terms in force.

Method Path Role Response
GET /merchant/affiliate owner, finance 200 your code, link, terms, figures and referral balance
GET /merchant/affiliate/referrals?limit=&offset= owner, finance 200 {"referrals": [referral, …], "total": n, "limit": 50, "offset": 0}, newest first
GET /merchant/affiliate/commissions?limit=&offset= owner, finance 200 {"commissions": [commission, …], "total": n, "limit": 50, "offset": 0}, newest first
POST /merchant/affiliate/payouts owner, finance 201 {"payout": payout}
GET /merchant/affiliate/payouts?status=&limit=&offset= owner, finance 200 {"payouts": [payout, …], "total": n, "limit": 50, "offset": 0}, newest first
GET /merchant/affiliate/payouts/{id} owner, finance 200 {"payout": payout}
POST /merchant/affiliate/payouts/{id}/cancel owner, finance 200 {"payout": payout}, while it is requested
POST /referrals/{code}/click no authentication 200 {"valid": true}

The referral program is live only: these routes take no mode.

GET /merchant/affiliate · owner, finance

The first call gives your account its referral code: 8 characters, lower-case letters and digits without the ones easy to misread (0, o, 1, l, i). It never changes. link is what you share: payitgo's signup page with ?ref=<code>. The answer also has your referral balance and how it is paid out.

curl -s https://api.payitgo.co/merchant/affiliate -H "Authorization: Bearer $ACCESS_TOKEN"

200 OK:

{
  "affiliate": {
    "merchant_id": "3f8a1c27b94e05d6a2c71e90",
    "merchant_name": "Acme Store",
    "code": "k7m2x9qa",
    "status": "active",
    "lifetime": false,
    "created_at": "2026-10-11T14:20:07.331Z",
    "updated_at": "2026-10-11T14:20:07.331Z"
  },
  "link": "https://payitgo.co/signup?ref=k7m2x9qa",
  "terms": {
    "rate_bps": 2500,
    "custom": false,
    "commission_bps": 2500,
    "boosted_bps": 3000,
    "boost_referrals": 5,
    "active_days": 30,
    "duration_months": 60,
    "lifetime": false
  },
  "stats": {
    "clicks": 214,
    "clicks_30d": 61,
    "referrals": 3,
    "referrals_30d": 1,
    "active_referrals": 2,
    "payments": 148,
    "volume_usd": "58240.75",
    "earned_usd": "145.601875",
    "earned_30d_usd": "12.415"
  },
  "balance": {
    "token": "USDT_TRC20",
    "chain": "tron",
    "available": "45.601875",
    "pending_payout": "100",
    "paid_out": "0",
    "earned": "145.601875"
  },
  "payouts": {
    "min_payout_usd": "50",
    "network_fee": "1.5",
    "payout_days": 5,
    "open": {
      "id": "9dfe8f5ae04f2aeab89957db",
      "affiliate_id": "3f8a1c27b94e05d6a2c71e90",
      "merchant_name": "Acme Store",
      "token": "USDT_TRC20",
      "chain": "tron",
      "amount": "100",
      "network_fee": "1.5",
      "send_amount": "98.5",
      "address_id": "3e7a1c9f5b2d4e8a6c0f2b1d",
      "address": "TWd4Kp9mR2xH7sN3vB6cJ8fL5qY1tZgEaU",
      "status": "requested",
      "due_at": "2026-10-22T06:00:41.03Z",
      "requested_by": "9b2e4c71d8a05f3e6c1a7b24",
      "created_at": "2026-10-15T06:00:41.03Z",
      "updated_at": "2026-10-15T06:00:41.03Z"
    }
  }
}
Field Description
affiliate.code, link Your referral code, and the link to share
affiliate.status active, or suspended by payitgo: your link is refused and your referrals earn nothing more
affiliate.commission_bps, affiliate.duration_months, affiliate.lifetime Terms of your own, when payitgo agreed them with you
terms.rate_bps What your referrals' payments earn you now, in basis points of payitgo's service fee (2500 = 25%)
terms.custom true with a rate of your own, which is never boosted
terms.commission_bps, terms.boosted_bps The rate, and the rate once you have terms.boost_referrals active referrals: referrals that earned you a commission in the last terms.active_days days
terms.duration_months, terms.lifetime How long each referral earns you, from its signup; lifetime: for as long as it uses payitgo
stats.clicks, stats.clicks_30d Visits through your link, ever and in the last 30 days
stats.referrals, stats.referrals_30d, stats.active_referrals Merchants who signed up through it, ever and in the last 30 days, and how many are active
stats.payments, stats.volume_usd Your referrals' live deposits that earned you a commission, and their total in dollars
stats.earned_usd, stats.earned_30d_usd What you earned, ever and in the last 30 days, in dollars (USDT)
balance.token, balance.chain Always USDT_TRC20, tron: your referral balance is held in USDT on Tron (1 USDT = $1)
balance.available What you can ask to be paid now
balance.pending_payout Asked for and not paid yet (a payout requested or processing)
balance.paid_out Paid to you so far
balance.earned Every commission ever: available + pending_payout + paid_out
payouts.min_payout_usd The least you can ask to be paid, in USDT
payouts.network_fee What sending a payout costs now, in USDT, taken from the payout. A quote: each payout's fee is fixed when you request it. "0" when it could not be quoted just now
payouts.payout_days The business days (Monday to Friday) within which payitgo pays a request
payouts.open Your payout that is requested or processing, if any; absent when none. You have one open at most

A developer or a viewer gets 403 {"error": "insufficient permissions"}.

When someone opens your link, the signup page calls POST /referrals/{code}/click (no authentication, any origin, 5 a second per IP address). It counts the visit in your clicks and answers {"valid": true} when the code is an active affiliate's, or {"valid": false} (and counts nothing) for an unknown or suspended code. The page keeps the code and sends it as referral_code when the account is opened (Open an account).

curl -s -X POST https://api.payitgo.co/referrals/k7m2x9qa/click

The new account is your referral from then on. Owners and finance get an affiliate:referral event and a "New referral" notification in the affiliates category (emailed to them by default).

What earns nothing

Referrals

GET /merchant/affiliate/referrals?limit=50&offset=0 · owner, finance

The merchants you referred, newest first, with what each earned you.

{
  "referrals": [
    {
      "merchant_id": "7c1e5a9d3b2f4086e1a7c5d9",
      "merchant_name": "Globex Games",
      "affiliate_id": "3f8a1c27b94e05d6a2c71e90",
      "code": "k7m2x9qa",
      "signed_up_at": "2026-10-12T08:41:55.120Z",
      "status": "active",
      "created_at": "2026-10-12T08:41:55.297Z",
      "earns_until": "2031-10-12T08:41:55.120Z",
      "payments": 37,
      "volume_usd": "4210.5",
      "earned_usd": "10.52625",
      "last_earned_at": "2026-10-14T10:02:11.402Z"
    }
  ],
  "total": 3,
  "limit": 50,
  "offset": 0
}

The referral object

Field Type Description
merchant_id, merchant_name string The merchant you referred
affiliate_id string Your account
code string The code it signed up with
signed_up_at string When it opened its account
status string active, or void: it earns you nothing more
void_reason, voided_at string, optional Why and when it was voided
attached_by string, optional Present when payitgo attached the merchant to your code by hand
created_at string When it became your referral
earns_until string or null When its payments stop earning you commissions; null: for as long as it uses payitgo
payments integer Its live deposits that earned you a commission
volume_usd, earned_usd decimal string Their total in dollars, and what they earned you
last_earned_at string, optional Its latest commission

Commissions

GET /merchant/affiliate/commissions?limit=50&offset=0 · owner, finance

Once a live deposit to a merchant you referred is settled and payitgo's fee on it is final, your share of that fee is credited to your referral balance in USDT on Tron: one commission per deposit. It is not in your account's balance or ledger; ask for a referral payout to be paid it. Newest first:

{
  "commissions": [
    {
      "id": "5b8e2a1c9d4f7e3a06c2b1d8:4be17c…9c0a",
      "affiliate_id": "3f8a1c27b94e05d6a2c71e90",
      "merchant_id": "7c1e5a9d3b2f4086e1a7c5d9",
      "merchant_name": "Globex Games",
      "payment_id": "5b8e2a1c9d4f7e3a06c2b1d8",
      "deposit_usd": "120",
      "fee_usd": "1.2",
      "rate_bps": 2500,
      "token": "USDT_TRC20",
      "amount": "0.3",
      "created_at": "2026-10-14T10:02:11.402Z"
    }
  ],
  "total": 148,
  "limit": 50,
  "offset": 0
}

The commission object

Field Type Description
id string The deposit it comes from, <payment id>:<transaction id>
affiliate_id string Your account
merchant_id, merchant_name string The referred merchant who was paid
payment_id string Their payment
deposit_usd decimal string The deposit, in dollars
fee_usd decimal string payitgo's service fee on it, in dollars
rate_bps integer Your share of the fee, in basis points
token string USDT_TRC20
amount decimal string What was credited to your referral balance: the fee times your rate
created_at string When it was credited

Referral balance and payouts

Your commissions build up in your referral balance, in USDT on Tron (USDT_TRC20), apart from your account's own balance: GET /merchant/balances and the ledger never show them, and a payout of your balance cannot take them. GET /merchant/affiliate gives the balance (balance) and the payout terms (payouts) (Your code and link).

Request a referral payout

POST /merchant/affiliate/payouts · owner, finance · 1 a second per user, in bursts of 5

Field Type Required Description
amount number or decimal string no How much of balance.available, at most 6 decimals. Absent or 0: all of it
address_id string no One of your live payout addresses, a USDT_TRC20 one
address string no Any other Tron address (T…, 34 characters)
password string with address Your sign-in password: the money goes somewhere that is not one of your payout addresses

Give address_id or address, not both. With neither, the payout goes to your default USDT_TRC20 payout address (the oldest one when none is marked default).

curl -s https://api.payitgo.co/merchant/affiliate/payouts \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"amount": "100"}'

201 Created. The amount is held from your referral balance at once:

{
  "payout": {
    "id": "9dfe8f5ae04f2aeab89957db",
    "affiliate_id": "3f8a1c27b94e05d6a2c71e90",
    "merchant_name": "Acme Store",
    "token": "USDT_TRC20",
    "chain": "tron",
    "amount": "100",
    "network_fee": "1.5",
    "send_amount": "98.5",
    "address_id": "3e7a1c9f5b2d4e8a6c0f2b1d",
    "address": "TWd4Kp9mR2xH7sN3vB6cJ8fL5qY1tZgEaU",
    "status": "requested",
    "due_at": "2026-10-22T06:00:41.03Z",
    "requested_by": "9b2e4c71d8a05f3e6c1a7b24",
    "created_at": "2026-10-15T06:00:41.03Z",
    "updated_at": "2026-10-15T06:00:41.03Z"
  }
}

To an address that is not one of your payout addresses, with your password (the answer has no address_id then):

curl -s https://api.payitgo.co/merchant/affiliate/payouts \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"amount": "100", "address": "TSeJkUh4Qv67VNFwY8LaAxERygNdy6NQZK", "password": "a-long-password"}'

Owners and finance are told of every request: a "Payout requested" notification in the affiliates category, emailed by default, saying how much, to which address and by when payitgo pays it. If nobody of your team asked for it, cancel it and change your password.

Errors, in the order they are checked:

Status Message Cause
403 insufficient permissions A developer or a viewer
429 rate limit exceeded More than 1 request a second
400 invalid request body Not JSON, or amount is not a number
403 password is incorrect address without your right password
409 a payout is open already: cancel it, or wait for it to be paid You have a payout requested or processing
400 give address_id or address, not both
400 address is not a valid Tron address
400 referral payouts are paid in USDT on Tron: choose a USDT (Tron) payout address address_id is an address of another coin
400 unknown payout address address_id is not one of your live payout addresses
400 add a USDT (Tron) payout address, or give the address to pay Neither given, and you have no live USDT_TRC20 payout address
400 amount must be more than 0 Negative
400 amount has more than 6 decimals
400 the minimum payout is 50.00 USDT_TRC20 Under payouts.min_payout_usd; also when you ask for all of it and available is under the minimum
400 amount must be more than the network fee of 1.5 USDT_TRC20 Nothing would be left to send
400 your referral balance holds 45.601875 USDT_TRC20 More than available
500 could not request the payout For example the network fee could not be quoted: try again a little later

The referral payout object

Field Type Description
id string The payout's id
affiliate_id, merchant_name string Your account
token, chain string Always USDT_TRC20, tron
amount decimal string Taken from your referral balance
network_fee decimal string The network fee of sending it, fixed when you asked, taken from amount
send_amount decimal string What reaches address: amount less network_fee
address_id string, optional The payout address you chose (or your default); absent for an address given with the request
address string The Tron address it is paid to, fixed when you asked (changing your payout address later does not move it)
status string requested, processing, paid, rejected or canceled (below)
due_at string When payitgo pays it by: payout_days business days after the request, at the same time of day (UTC; Saturdays and Sundays skipped)
requested_by string The user who asked for it
tx_id string, optional The Tron transaction that paid it, once paid
note string, optional Why payitgo rejected it; sent by hand when payitgo paid it outside the usual flow
approved_at string, optional When payitgo started sending it
paid_at, rejected_at string, optional When it was paid, or rejected
canceled_by, canceled_at string, optional Who cancelled it, and when
created_at, updated_at string When it was requested and last changed
status Means
requested Waiting for payitgo. The amount is held: out of available, in pending_payout. You can cancel it
processing payitgo is sending it: it can no longer be cancelled. It becomes paid once the transaction is on chain. Should it fail to be sent, payitgo may reject it, and the amount comes back
paid Paid: tx_id is the transaction. The amount moves to paid_out
rejected Refused by payitgo; note says why. The amount is back in available
canceled Cancelled by your team. The amount is back in available

paid, rejected and canceled are final.

Follow and cancel referral payouts

Method Path Role Response
GET /merchant/affiliate/payouts?status=&limit=&offset= owner, finance 200 {"payouts": [payout, …], "total": n, "limit": 50, "offset": 0}. Newest first; with status=requested or status=processing, soonest due first. limit 1 to 200
GET /merchant/affiliate/payouts/{id} owner, finance 200 {"payout": payout}
POST /merchant/affiliate/payouts/{id}/cancel owner, finance 200 {"payout": payout} with "status": "canceled", while it is still requested. The amount is back in available
curl -s https://api.payitgo.co/merchant/affiliate/payouts/9dfe8f5ae04f2aeab89957db -H "Authorization: Bearer $ACCESS_TOKEN"

200 OK, a payout payitgo has paid:

{
  "payout": {
    "id": "9dfe8f5ae04f2aeab89957db",
    "affiliate_id": "3f8a1c27b94e05d6a2c71e90",
    "merchant_name": "Acme Store",
    "token": "USDT_TRC20",
    "chain": "tron",
    "amount": "100",
    "network_fee": "1.5",
    "send_amount": "98.5",
    "address_id": "3e7a1c9f5b2d4e8a6c0f2b1d",
    "address": "TWd4Kp9mR2xH7sN3vB6cJ8fL5qY1tZgEaU",
    "status": "paid",
    "due_at": "2026-10-22T06:00:41.03Z",
    "requested_by": "9b2e4c71d8a05f3e6c1a7b24",
    "tx_id": "9f3c5a0e7d2b41c8a6e5f0d3b2a1c9e8d7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2",
    "approved_at": "2026-10-19T09:12:05.418Z",
    "paid_at": "2026-10-19T09:13:02.771Z",
    "created_at": "2026-10-15T06:00:41.03Z",
    "updated_at": "2026-10-19T09:13:02.771Z"
  }
}
curl -s -X POST https://api.payitgo.co/merchant/affiliate/payouts/9dfe8f5ae04f2aeab89957db/cancel \
  -H "Authorization: Bearer $ACCESS_TOKEN"
Status Message Cause
403 insufficient permissions A developer or a viewer
404 not found No such payout of yours
409 payout is not in a state that allows this Cancelling a payout that is no longer requested
500 could not list the payouts · could not read the payout · could not cancel the payout

Besides "Payout requested", owners and finance get these affiliates notifications:

Title When Body
Payout paid It is paid payitgo paid 98.5 USDT_TRC20 of your referral balance to TWd4Kp9mR2xH7sN3vB6cJ8fL5qY1tZgEaU (transaction 9f3c…a3b2).
Payout rejected It is rejected Your payout of 100 USDT_TRC20 was rejected: <reason>. It is back in your referral balance.

On the dashboard socket, owners and finance get affiliate:balance (the balance object) whenever the referral balance changes, and affiliate:payout (the payout) on every change of a payout.

Real-time events

A dashboard can stay live without polling: connect socket.io (a 4.x client) to the /merchant namespace with an access token. Each event carries the same object the REST route returns, so you upsert it by id. Test and live arrive on the same connection: filter on livemode (mode on notifications).

import { io } from "socket.io-client";

// auth as a function: every (re)connection sends the access token you hold now
const socket = io("https://api.payitgo.co/merchant", { auth: (cb) => cb({ token: accessToken }), transports: ["websocket"] });
socket.on("payment:update", (e) => upsertPayment(e.payment)); // e.type: payment.created, payment.confirmed…
socket.on("dashboard:update", (d) => { if (d.livemode === live) showFigures(d); });
socket.on("auth:error", async ({ code }) => {
  if (code === "token_expired") { await refreshAccessToken(); socket.connect(); } // the server has disconnected
  else signOut(); // session_revoked, role_changed, removed, suspended
});
Event Payload Who gets it
payment:update {"type", "payment"}: the whole payment after each change, as GET /merchant/payments lists it. The checkout's event plus merchant_id and payment.created Everyone
dashboard:update The dashboard figures, with livemode. At most one every 2 seconds per mode Everyone
balance:update {"livemode", "balances"}, as GET /merchant/balances: your account's own balances, never the referral balance Everyone
settlement:update A settlement, on every change Everyone
payout:update A payout: requested, canceled, approved, rejected, sent. It can also arrive with nothing you see changed (payitgo sending it): upsert it all the same Everyone
payout_address:update {"address", "deleted"} Everyone
webhook:delivery A delivery, on every attempt Everyone
webhook:update The webhook, without its secret Owners, developers
notification, notification:read A new notification; the user read some or all of them (ids, all, livemode, unread) By category (support: everyone; affiliates: owners, finance); the user who read them
merchant:update The merchant Everyone
store:update, api_key:update A store; an API key (never its secret) Everyone; owners and developers
team:update {"action", "user", "invitation"}: a member or an invitation changed Owners
link:update, customer:update A payment link; a customer Everyone
audit:entry An audit log entry Owners
support:message, support:message:update A new support chat message; one edited or deleted, whole, to replace the one shown Everyone
support:read, support:typing Support or a team member read the chat ({"side", "read_at", "unread"}); someone at support started or stopped typing ({"side", "name", "typing"}) (Live chat updates) Everyone
affiliate:referral, affiliate:commission A referral: a merchant signed up through your link, or a referral was voided; a commission just credited (an affiliate:balance follows) Owners, finance
affiliate:balance Your referral balance, the balance object of GET /merchant/affiliate, whenever it changes: a commission credited, a payout requested, cancelled, rejected or paid Owners, finance
affiliate:payout A referral payout, on every change: requested, cancelled, being sent (processing), paid, rejected. It can also arrive with nothing you see changed (payitgo's transfer moving on): upsert it all the same Owners, finance

Besides auth:refresh (below), the dashboard sends one event of its own: support:typing, while someone types in the support chat (Live chat updates).

On connecting, the socket receives session: who it is and its token's expires_at. The socket lasts as long as that token. To keep it open, refresh the session (POST /merchant/auth/refresh) a minute before and send the new token on the open socket: await socket.emitWithAck("auth:refresh", newToken) answers {"ok": true, "session": {…}}. Otherwise, at expires_at the server sends auth:error with token_expired and disconnects (socket.io-client does not reconnect after a server disconnect by itself): refresh, then connect again.

Signing out, a password change, a role change, removal from the team and a suspension end the socket at once, with auth:error session_revoked, role_changed, removed or suspended: sign in again. A refused handshake is a connect_error unauthorized with err.data.code missing_token, invalid_token or wrong_kind. A connection dropped for under 2 minutes is recovered: the events missed are replayed, and session.recovered is true. After a longer gap, load your screens again.