WaaS 30-Minute Quickstart
Run through a complete WaaS integration path in around 30 minutes.
This guide helps you run a complete WaaS flow in about 30 minutes: create a deposit address, receive deposit callbacks, submit payout, and confirm final payout status.
Acceptance Criteria
- Successfully call
/api/v1/address/createto create addresses. - Webhook receiver verifies
signand returnssuccess. - Successfully call
/api/v1/payoutand persist returnedcid. - Determine final status via callback or
/api/v1/payout/query(terminal statuses:2/4/6/7).
Step 0: Prepare inputs (~5 minutes)
- BASE_URL: project-specific Base URL (copy it from Cregis App > WaaS Project > Project Settings > Base URL).
- PID: WaaS project ID.
- API_KEY: project API key.
- CHAIN_ID: chain identifier (for example
195). - CURRENCY: token identifier (for example
195@195). - CALLBACK_URL: publicly reachable webhook URL.
Related docs:
Step 1: Start a minimal webhook receiver (~8 minutes)
Use the Node.js / Python / PHP examples below to implement signature verification and idempotency handling.
Important: after successful processing, return HTTP 200 with plain text success.import express from 'express';
import crypto from 'crypto';
const app = express();
app.use(express.json());
const API_KEY = process.env.CREGIS_API_KEY;
const processed = new Set(); // Use a database unique key in production
function buildSign(apiKey, payload) {
const canonical = Object.keys(payload)
.filter((k) => k !== 'sign' && payload[k] !== '' && payload[k] !== null && payload[k] !== undefined)
.sort()
.map((k) => `${k}${payload[k]}`)
.join('');
return crypto
.createHash('md5')
.update(`${apiKey}${canonical}`)
.digest('hex')
.toLowerCase();
}
app.post('/webhook/waas', (req, res) => {
const body = req.body || {};
if (buildSign(API_KEY, body) !== body.sign) {
return res.status(401).send('invalid sign');
}
const uniqueKey = `${body.cid || 'no-cid'}:${body.txid || 'no-txid'}:${body.status || 'no-status'}`;
if (processed.has(uniqueKey)) {
return res.status(200).send('success');
}
processed.add(uniqueKey);
// TODO: apply your business logic here (credit, payout final state, alerts, etc.)
return res.status(200).send('success');
});
app.listen(8080, () => console.log('WaaS webhook server running on :8080'));import hashlib
import os
from flask import Flask, request
app = Flask(__name__)
API_KEY = os.environ['CREGIS_API_KEY']
processed = set() # Use a database unique key in production
def build_sign(api_key, payload):
canonical = ''.join(
f'{key}{payload[key]}'
for key in sorted(payload)
if key != 'sign' and payload[key] not in ('', None)
)
return hashlib.md5(f'{api_key}{canonical}'.encode('utf-8')).hexdigest().lower()
@app.post('/webhook/waas')
def waas_webhook():
body = request.get_json(silent=True) or {}
if build_sign(API_KEY, body) != body.get('sign'):
return 'invalid sign', 401
unique_key = f"{body.get('cid', 'no-cid')}:{body.get('txid', 'no-txid')}:{body.get('status', 'no-status')}"
if unique_key in processed:
return 'success', 200
processed.add(unique_key)
# TODO: apply your business logic here (credit, payout final state, alerts, etc.)
return 'success', 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)<?php
$apiKey = getenv('CREGIS_API_KEY');
$body = json_decode(file_get_contents('php://input'), true) ?: [];
function buildSign(string $apiKey, array $payload): string
{
$sorted = $payload;
unset($sorted['sign']);
ksort($sorted, SORT_STRING);
$canonical = '';
foreach ($sorted as $key => $value) {
if ($value === '' || $value === null) {
continue;
}
$canonical .= $key . $value;
}
return strtolower(md5($apiKey . $canonical));
}
if (buildSign($apiKey, $body) !== ($body['sign'] ?? null)) {
http_response_code(401);
echo 'invalid sign';
exit;
}
$uniqueKey = ($body['cid'] ?? 'no-cid') . ':' . ($body['txid'] ?? 'no-txid') . ':' . ($body['status'] ?? 'no-status');
// Production: store $uniqueKey in a database/Redis unique index for idempotency.
// TODO: apply your business logic here (credit, payout final state, alerts, etc.)
http_response_code(200);
echo 'success';Step 2: Create a deposit address (~5 minutes)
Call /api/v1/address/create and bind the callback URL for this business flow.
TON and XRP do not support creating addresses via API requests. Create them manually in the client. See Create Sub-address.
curl -X POST "${BASE_URL}/api/v1/address/create" \
-H "Content-Type: application/json" \
-d '{
"pid": 1382528827416576,
"chain_id": "195",
"alias": "user_10001",
"callback_url": "https://your-domain.com/webhook/waas",
"nonce": "n6k2q1",
"timestamp": 1776391200000,
"sign": "<calculated-signature>"
}'import crypto from 'crypto';
const BASE_URL = process.env.CREGIS_BASE_URL;
const API_KEY = process.env.CREGIS_API_KEY;
function buildSignedBody(apiKey, bizBody) {
const body = {
...bizBody,
nonce: crypto.randomUUID().replaceAll('-', '').slice(0, 8),
timestamp: Date.now(),
};
const canonical = Object.keys(body)
.filter((k) => body[k] !== '' && body[k] !== null && body[k] !== undefined)
.sort()
.map((k) => `${k}${body[k]}`)
.join('');
const sign = crypto
.createHash('md5')
.update(`${apiKey}${canonical}`)
.digest('hex')
.toLowerCase();
return { ...body, sign };
}
const body = buildSignedBody(API_KEY, {
"pid": 1382528827416576,
"chain_id": "195",
"alias": "user_10001",
"callback_url": "https://your-domain.com/webhook/waas"
});
const response = await fetch(`${BASE_URL}/api/v1/address/create`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
console.log(await response.json());import hashlib
import os
import secrets
import time
import requests
BASE_URL = os.environ['CREGIS_BASE_URL']
API_KEY = os.environ['CREGIS_API_KEY']
def build_signed_body(api_key, biz_body):
body = {
**biz_body,
'nonce': secrets.token_hex(4),
'timestamp': int(time.time() * 1000),
}
canonical = ''.join(
f'{key}{body[key]}'
for key in sorted(body)
if body[key] not in ('', None)
)
body['sign'] = hashlib.md5(f'{api_key}{canonical}'.encode('utf-8')).hexdigest().lower()
return body
body = build_signed_body(API_KEY, {
'pid': 1382528827416576,
'chain_id': '195',
'alias': 'user_10001',
'callback_url': 'https://your-domain.com/webhook/waas',
})
headers = {'Content-Type': 'application/json'}
response = requests.post(f'{BASE_URL}/api/v1/address/create', json=body, headers=headers, timeout=10)
print(response.json())<?php
$baseUrl = getenv('CREGIS_BASE_URL');
$apiKey = getenv('CREGIS_API_KEY');
function buildSignedBody(string $apiKey, array $bizBody): array
{
$body = $bizBody;
$body['nonce'] = bin2hex(random_bytes(4));
$body['timestamp'] = (int) round(microtime(true) * 1000);
$sorted = $body;
ksort($sorted, SORT_STRING);
$canonical = '';
foreach ($sorted as $key => $value) {
if ($value === '' || $value === null) {
continue;
}
$canonical .= $key . $value;
}
$body['sign'] = strtolower(md5($apiKey . $canonical));
return $body;
}
$body = buildSignedBody($apiKey, [
'pid' => 1382528827416576,
'chain_id' => '195',
'alias' => 'user_10001',
'callback_url' => 'https://your-domain.com/webhook/waas',
]);
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\n",
'content' => json_encode($body, JSON_UNESCAPED_SLASHES),
'timeout' => 10,
],
]);
echo file_get_contents($baseUrl . '/api/v1/address/create', false, $context);Persist address ownership mapping after successful response so later deposits can be reconciled to the right user.
Step 3: Validate deposit callback (~5 minutes)
When funds arrive on-chain, WaaS sends callback payloads to your endpoint.
- Typical fields include
cid,txid,amount, andstatus. - For deposit success callbacks, status is typically
1.
- Verify signature first, then update business ledger.
- Use
cid/txididempotency keys to prevent double-credit. - Return
successimmediately on successful processing.
Step 4: Submit payout (~5 minutes)
Call /api/v1/payout. Use your own unique order ID as third_party_id for idempotency and reconciliation.
curl -X POST "${BASE_URL}/api/v1/payout" \
-H "Content-Type: application/json" \
-d '{
"pid": 1382528827416576,
"currency": "195@195",
"address": "TXsmKpEuW7qWnXzJLGP9eDLvWPR2GRn1FS",
"amount": "1.1",
"third_party_id": "withdraw_20260417_0001",
"callback_url": "https://your-domain.com/webhook/waas",
"remark": "user withdrawal",
"nonce": "p8x9m2",
"timestamp": 1776391300000,
"sign": "<calculated-signature>"
}'import crypto from 'crypto';
const BASE_URL = process.env.CREGIS_BASE_URL;
const API_KEY = process.env.CREGIS_API_KEY;
function buildSignedBody(apiKey, bizBody) {
const body = {
...bizBody,
nonce: crypto.randomUUID().replaceAll('-', '').slice(0, 8),
timestamp: Date.now(),
};
const canonical = Object.keys(body)
.filter((k) => body[k] !== '' && body[k] !== null && body[k] !== undefined)
.sort()
.map((k) => `${k}${body[k]}`)
.join('');
const sign = crypto
.createHash('md5')
.update(`${apiKey}${canonical}`)
.digest('hex')
.toLowerCase();
return { ...body, sign };
}
const body = buildSignedBody(API_KEY, {
"pid": 1382528827416576,
"currency": "195@195",
"address": "TXsmKpEuW7qWnXzJLGP9eDLvWPR2GRn1FS",
"amount": "1.1",
"third_party_id": "withdraw_20260417_0001",
"callback_url": "https://your-domain.com/webhook/waas",
"remark": "user withdrawal"
});
const response = await fetch(`${BASE_URL}/api/v1/payout`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
console.log(await response.json());import hashlib
import os
import secrets
import time
import requests
BASE_URL = os.environ['CREGIS_BASE_URL']
API_KEY = os.environ['CREGIS_API_KEY']
def build_signed_body(api_key, biz_body):
body = {
**biz_body,
'nonce': secrets.token_hex(4),
'timestamp': int(time.time() * 1000),
}
canonical = ''.join(
f'{key}{body[key]}'
for key in sorted(body)
if body[key] not in ('', None)
)
body['sign'] = hashlib.md5(f'{api_key}{canonical}'.encode('utf-8')).hexdigest().lower()
return body
body = build_signed_body(API_KEY, {
'pid': 1382528827416576,
'currency': '195@195',
'address': 'TXsmKpEuW7qWnXzJLGP9eDLvWPR2GRn1FS',
'amount': '1.1',
'third_party_id': 'withdraw_20260417_0001',
'callback_url': 'https://your-domain.com/webhook/waas',
'remark': 'user withdrawal',
})
headers = {'Content-Type': 'application/json'}
response = requests.post(f'{BASE_URL}/api/v1/payout', json=body, headers=headers, timeout=10)
print(response.json())<?php
$baseUrl = getenv('CREGIS_BASE_URL');
$apiKey = getenv('CREGIS_API_KEY');
function buildSignedBody(string $apiKey, array $bizBody): array
{
$body = $bizBody;
$body['nonce'] = bin2hex(random_bytes(4));
$body['timestamp'] = (int) round(microtime(true) * 1000);
$sorted = $body;
ksort($sorted, SORT_STRING);
$canonical = '';
foreach ($sorted as $key => $value) {
if ($value === '' || $value === null) {
continue;
}
$canonical .= $key . $value;
}
$body['sign'] = strtolower(md5($apiKey . $canonical));
return $body;
}
$body = buildSignedBody($apiKey, [
'pid' => 1382528827416576,
'currency' => '195@195',
'address' => 'TXsmKpEuW7qWnXzJLGP9eDLvWPR2GRn1FS',
'amount' => '1.1',
'third_party_id' => 'withdraw_20260417_0001',
'callback_url' => 'https://your-domain.com/webhook/waas',
'remark' => 'user withdrawal',
]);
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\n",
'content' => json_encode($body, JSON_UNESCAPED_SLASHES),
'timeout' => 10,
],
]);
echo file_get_contents($baseUrl . '/api/v1/payout', false, $context);Persist returned cid as the primary key for status tracking.
Step 5: Confirm final payout status (~2 minutes)
- Callback-first: terminal statuses are
2/4/6/7and mutually exclusive. - Query fallback: call
/api/v1/payout/querybycid.
curl -X POST "${BASE_URL}/api/v1/payout/query" \
-H "Content-Type: application/json" \
-d '{
"pid": 1382528827416576,
"cid": 1391751691788288,
"nonce": "a1b2c3",
"timestamp": 1776391400000,
"sign": "<calculated-signature>"
}'import crypto from 'crypto';
const BASE_URL = process.env.CREGIS_BASE_URL;
const API_KEY = process.env.CREGIS_API_KEY;
function buildSignedBody(apiKey, bizBody) {
const body = {
...bizBody,
nonce: crypto.randomUUID().replaceAll('-', '').slice(0, 8),
timestamp: Date.now(),
};
const canonical = Object.keys(body)
.filter((k) => body[k] !== '' && body[k] !== null && body[k] !== undefined)
.sort()
.map((k) => `${k}${body[k]}`)
.join('');
const sign = crypto
.createHash('md5')
.update(`${apiKey}${canonical}`)
.digest('hex')
.toLowerCase();
return { ...body, sign };
}
const body = buildSignedBody(API_KEY, {
"pid": 1382528827416576,
"cid": 1391751691788288
});
const response = await fetch(`${BASE_URL}/api/v1/payout/query`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
console.log(await response.json());import hashlib
import os
import secrets
import time
import requests
BASE_URL = os.environ['CREGIS_BASE_URL']
API_KEY = os.environ['CREGIS_API_KEY']
def build_signed_body(api_key, biz_body):
body = {
**biz_body,
'nonce': secrets.token_hex(4),
'timestamp': int(time.time() * 1000),
}
canonical = ''.join(
f'{key}{body[key]}'
for key in sorted(body)
if body[key] not in ('', None)
)
body['sign'] = hashlib.md5(f'{api_key}{canonical}'.encode('utf-8')).hexdigest().lower()
return body
body = build_signed_body(API_KEY, {
'pid': 1382528827416576,
'cid': 1391751691788288,
})
headers = {'Content-Type': 'application/json'}
response = requests.post(f'{BASE_URL}/api/v1/payout/query', json=body, headers=headers, timeout=10)
print(response.json())<?php
$baseUrl = getenv('CREGIS_BASE_URL');
$apiKey = getenv('CREGIS_API_KEY');
function buildSignedBody(string $apiKey, array $bizBody): array
{
$body = $bizBody;
$body['nonce'] = bin2hex(random_bytes(4));
$body['timestamp'] = (int) round(microtime(true) * 1000);
$sorted = $body;
ksort($sorted, SORT_STRING);
$canonical = '';
foreach ($sorted as $key => $value) {
if ($value === '' || $value === null) {
continue;
}
$canonical .= $key . $value;
}
$body['sign'] = strtolower(md5($apiKey . $canonical));
return $body;
}
$body = buildSignedBody($apiKey, [
'pid' => 1382528827416576,
'cid' => 1391751691788288,
]);
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\n",
'content' => json_encode($body, JSON_UNESCAPED_SLASHES),
'timeout' => 10,
],
]);
echo file_get_contents($baseUrl . '/api/v1/payout/query', false, $context);Go-live Checklist
- Separate development and production credentials.
- Webhook endpoint has signature verification, idempotency, monitoring, and alerting.
- All fund actions (deposit/payout) use unique business keys.
- Logs are searchable by
cid,third_party_id, andtxid. - Business branches are defined for all terminal statuses
2/4/6/7.
Common Troubleshooting
1) Why am I not receiving callbacks?
Check callback URL reachability, ensure your handler returns plain text success, and verify gateways/firewalls are not blocking requests.
2) Why do I receive duplicate callbacks?
If your handler receives a payload that has already been processed, handle it idempotently. Use strict idempotency with cid/txid.
3) Why does signature verification fail frequently?
Validate sorting order, empty-value filtering, exclusion of sign, and lowercase MD5 output.
Next recommended reading: WaaS Business Flow, WaaS Error Codes, and Supported Networks & Tokens.