Quickstart

From nothing to a paid payment in five minutes, without owning any USDT.

Everything here runs in test mode against a simulated chain. No real money, no wallet, no testnet faucet.

1. Get a test key

Sign in to the console at /app, open API keys, and create one. It starts with sk_test_. The prefix is what decides the mode — there is no header or flag that can turn a test key into a live one.

export PAYGATE=https://api.paygatehq.com
export KEY=sk_test_...

2. Create a payment

curl -X POST $PAYGATE/v1/payments \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"amount_decimal":"25.00","reference":"order_1"}'

You get back a payment. The parts that matter:

{
  "id": "0d1f…",
  "object": "payment",
  "status": "requires_payment",
  "amount": 25000000,
  "amount_decimal": "25.000000",
  "currency": "USDT",
  "checkout_url": "https://pay.paygatehq.com/c/cs_…",
  "routes": [
    { "chain": "sim", "address": "0x…", "amount": 25000000, "amount_decimal": "25.000000" }
  ],
  "expires_at": 1756300000
}

checkout_url is the hosted page. Send the customer there and you are done — it shows a QR code, the exact amount, and live confirmation progress.

When the payment completes, the page does what you choose. By default it stays on the receipt. Create the payment with a success_url and it sends the customer there; add ?close=1 to the link — for a page you opened in a new tab — and it closes that tab. Either way it counts down five seconds first, and delay= on the link sets 0 to 60, as in ?close=1&delay=10.

3. Pay it, without any USDT

curl -X POST $PAYGATE/v1/test/payments/{id}/pay \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)" -d '{}'

The simulated chain accepts the transfer and the indexer picks it up. Poll the payment and watch it move:

curl $PAYGATE/v1/payments/{id} -H "Authorization: Bearer $KEY"

requires_payment → detected → confirming → paid.

To move confirmations along without waiting, advance the simulated chain:

curl -X POST $PAYGATE/v1/test/advance \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"blocks":12}'

4. Receive the webhook

Register an endpoint, then have every event delivered to it:

curl -X POST $PAYGATE/v1/webhook_endpoints \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://your.app/hooks/paygate","events":["payment.paid","payment.reversed"]}'

The response carries the signing secret, once. Store it — verifying the signature is not optional, and the guide on Webhooks has the code for it in three languages.

Send yourself a test delivery without creating anything:

curl -X POST $PAYGATE/v1/webhook_endpoints/{id}/test \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)" -d '{}'

5. Rehearse the bad day

The three cases nobody can otherwise test:

# a reorg deeper than the confirmation window — paid becomes reversed
curl -X POST $PAYGATE/v1/test/payments/{id}/reorg -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)" -d '{}'

Underpayment and late payment are reachable the same way — see Test mode.

Then

Point the same code at a sk_live_ key. Nothing else changes: same routes, same shapes, same events. That is the whole reason test mode is a key prefix and not a separate host.