Guide
Go-live checklist
What to verify before real money flows, and a smoke test that proves your setup in one call.
Before real money
Credentials
- The API secret lives in a secret store, on backend hosts only — never in a repository, a mobile app or a page.
- Your
signmatches the test vector. - Every host that calls us keeps time with NTP. Requests more than 5 minutes off are refused.
- If you use an IP allowlist, it names every egress address, workers included.
- The smoke test passes from each of those hosts.
Deposits
- Every create sends a unique
external_order_id, and a retry reuses it. - You credit on the webhook, never on a redirect, an iframe event or a
processingstep. - Wallet-address deposits credit
credited, notamount— see Wallet. - Your cashier shows the network next to every address, so a player does not send BEP-20 USDT to a Tron address.
Webhooks
- The callback URL is HTTPS and reachable from the internet. It is set in the admin panel, or per request with
callback_url. - Your handler verifies the signature on the raw body, and rejects the webhook test vector with a single byte changed.
- It deduplicates on
X-Idempotency-Key, answers2xxwithin a few seconds and processes afterwards. - You reconcile anything still open after an hour with the status endpoints, in case your endpoint was down through all eight attempts.
Payouts
- A payout wallet is configured for each family you pay out on — EVM and Tron are separate addresses.
- Its private key is held where only the signing service can use it: an HSM, a KMS or a hardware wallet, not an environment variable on a web server.
- It holds the tokens, and the routers have an allowance — the single router and the batch router separately.
- Before signing, your code compares every recipient and amount in
typed_datawith your own withdrawal records. - You handle
insufficient_allowanceandinsufficient_funds— see handling errors. - You send a unique
external_idper withdrawal, and move a withdrawal to paid only onpayout.completed.
First real transactions
- One small deposit on each network and each flow you use, followed through to the credited ledger entry.
- One small payout and one two-item batch on each network, followed through to
payout.completed. - One deliberately failed case: a payout above your allowance, to see your handling end to end.
Smoke test
One read-only signed call. If it succeeds, the key, the secret, the signature code, the clock and the IP allowlist are all right; if it fails, the error code says which. It moves nothing, so run it from every host and in your deploy pipeline.
func main() {
client := &time2pay.Client{BaseURL: os.Getenv("BASE_URL"), KeyID: os.Getenv("API_KEY_ID"), Secret: os.Getenv("API_KEY_SECRET")}
res, err := client.Call(context.Background(), "GET", "/v1/balance", "")
if err != nil {
log.Fatalf("not ready: %v", err)
}
log.Printf("key, signature, clock and IP allowlist all fine; settlement wallet %v", res["wallet"])
}<?php
require __DIR__ . '/Time2payClient.php';
$client = new Time2payClient(getenv('BASE_URL'), getenv('API_KEY_ID'), getenv('API_KEY_SECRET'));
try {
$res = $client->call('GET', '/v1/balance');
echo "key, signature, clock and IP allowlist all fine; settlement wallet {$res['wallet']}\n";
} catch (Throwable $e) {
fwrite(STDERR, "not ready: {$e->getMessage()}\n");
exit(1);
}import { createClient } from "./time2pay.js";
const client = createClient({
baseUrl: Deno.env.get("BASE_URL"),
keyId: Deno.env.get("API_KEY_ID"),
secret: Deno.env.get("API_KEY_SECRET"),
});
const res = await client.call("GET", "/v1/balance");
console.log(`key, signature, clock and IP allowlist all fine; settlement wallet ${res.wallet}`);import os
import sys
from time2pay import Time2payClient
client = Time2payClient(os.environ["BASE_URL"], os.environ["API_KEY_ID"], os.environ["API_KEY_SECRET"])
try:
res = client.call("GET", "/v1/balance")
except Exception as e:
sys.exit(f"not ready: {e}")
print(f"key, signature, clock and IP allowlist all fine; settlement wallet {res['wallet']}")import { Time2payClient } from "./time2pay.mjs";
const client = new Time2payClient({
baseUrl: process.env.BASE_URL,
keyId: process.env.API_KEY_ID,
secret: process.env.API_KEY_SECRET,
});
const res = await client.call("GET", "/v1/balance");
console.log(`key, signature, clock and IP allowlist all fine; settlement wallet ${res.wallet}`);| Result | Meaning |
|---|---|
200 | Ready. |
400 validation, "no settlement wallet configured" | Key, signature, clock and allowlist are all fine — the request got past every check. Your merchant simply has no EVM settlement wallet, which is normal if you only use Tron. Count it as a pass. |
401 unauthorized | Wrong key id or secret, a signing bug (check the test vector), or a clock more than 5 minutes off. |
403 forbidden | This host's IP is not in your allowlist. |
503 unavailable | Your key is fine; we cannot read the chain right now. Retry. |