Hello there! In this guide, you will find all the information required for integrating your platform with TopWallet and avail of its core functionalities.
We’ve tried to make the guide simple and easy to follow, with as much detail as possible and we have a wide array of api’s to select. If you have any questions, contact us at support@topconnect.io. We would love to help you out.
TopWallet provides a highly efficient solution to help organizations scale their payment disbursement and acceptance modules for both - conventional payments or digital assets.
Fast: We have optimized our solution for speed - You can go live in an afternoon and your customers can onboard in seconds.
High quality: In-house, world class AI models and a UI designed for low drop offs.
Flexible: Allows you to change branding, texts & workflows with zero code
Resilient: Our uptime has consistently been above 99.9%
Scalable: Capable of handling volume spikes as it is auto scalable
Secure: Annual 3rd party audits, VAPT, ISO certification, end to end data encryption
Additonally:
You can use the dashboard to access credentials or configure the solution as per your requirements. This dashboard also lets you view the application data and corresponding analytics.
We have engineered the SDK’s & dashboard to work together, so that you can finish your integration with just a few lines of code.
We have engineered the API's & dashboard to work together, so that you can finish your integration with just a few lines of code!
All API requests must be signed with your API Key and Secret Key using HMAC-SHA256.
ak_...) identifies your account and is sent with every request.sk_...) is used to compute the signature. Never send it in a request, and never put it in browser or mobile app code.| Header | Description | Example |
|---|---|---|
X-API-Key | Your API key | ak_test_51HxQ2EXAMPLEKEY |
X-Timestamp | Current Unix time in seconds (UTC) | 1760610000 |
X-Nonce | Random, unique string per request (16+ bytes, hex). Never reuse. | 7c2f9e1a4b8d3c6e5f0a1b2c3d4e5f60 |
X-Signature | Lowercase hex HMAC-SHA256 signature (see below) | 2b2b6750...0f58c1 |
Content-Type | application/json for requests with a body | application/json |
Compute the SHA-256 hash of the exact raw body bytes you will send, as lowercase hex.
For requests with no body (e.g. POST), hash an empty string:
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Sign the exact string you send. If you serialize JSON, sign it and then send that same string. Re-serializing can change spacing or key order and break the signature.
Join these five values with a newline (\n), with no trailing newline:
HTTP_METHOD (uppercase, e.g. POST)
REQUEST_PATH (path , e.g. /v2/topconnect/api/generate_checkout_hash)
TIMESTAMP (same value as X-Timestamp)
NONCE (same value as X-Nonce)
BODY_SHA256 (from Step 1)
signature = lowercase_hex( HMAC_SHA256( key = SECRET_KEY, message = string_to_sign ) )
Send the result in the X-Signature header.
Use these values to test your implementation. If your code produces the same signature, it's correct.
Credentials (test only):
API Key: ak_test_51HxQ2EXAMPLEKEY
Secret Key: sk_test_9fA3kL0EXAMPLESECRET
Request:
POST /v1/orders
Body: {"amount": "100","invoice_number": "INVOICE1000",
"notify_url": "https://google.io/"
}
Timestamp: 1760610000
Nonce: 7c2f9e1a4b8d3c6e5f0a1b2c3d4e5f60
Body SHA-256:
f0dddce8b8b9e1171674ba3f0b4d5421cf2439a6e5b8f818f898630c558882a3
String to sign:
POST
/v2/topconnect/api/generate_checkout_hash
1760610000
7c2f9e1a4b8d3c6e5f0a1b2c3d4e5f60
f0dddce8b8b9e1171674ba3f0b4d5421cf2439a6e5b8f818f898630c558882a3
Signature:
2b2b67508bb8a3e5bfffb9f88c41baee90430cc0089b1a65eafab0494f0f58c1
Full request:
curl -X POST "{{url}}/b2bapi/checkout_qr" \
-H "Content-Type: application/json" \
-H "X-API-Key: ak_test_51HxQ2EXAMPLEKEY" \
-H "X-Timestamp: 1760610000" \
-H "X-Nonce: 7c2f9e1a4b8d3c6e5f0a1b2c3d4e5f60" \
-H "X-Signature: 2b2b67508bb8a3e5bfffb9f88c41baee90430cc0089b1a65eafab0494f0f58c1" \
-d '{
"amount": "100",
"invoice_number": "INVOICE1000",
"notify_url": "htttps://google.ph/return"
}
'
const crypto = require('crypto');
const API_KEY = process.env.API_KEY;
const SECRET_KEY = process.env.SECRET_KEY;
const BASE_URL = 'https://api.example.com';
async function apiRequest(method, path, bodyObj) {
const body = bodyObj ? JSON.stringify(bodyObj) : '';
const timestamp = Math.floor(Date.now() / 1000).toString();
const nonce = crypto.randomBytes(16).toString('hex');
const bodyHash = crypto.createHash('sha256').update(body).digest('hex');
const stringToSign = [method.toUpperCase(), path, timestamp, nonce, bodyHash].join('\n');
const signature = crypto.createHmac('sha256', SECRET_KEY).update(stringToSign).digest('hex');
const res = await fetch(BASE_URL + path, {
method,
headers: {
'Content-Type': 'application/json',
'X-API-Key': API_KEY,
'X-Timestamp': timestamp,
'X-Nonce': nonce,
'X-Signature': signature,
},
body: body || undefined,
});
return res.json();
}
// Usage
apiRequest('POST', '/v2/topconnect/api/generate_checkout_hash', { amount: 1500, currency: 'PESO' }).then(console.log);
import hashlib, hmac, json, os, secrets, time
import requests
API_KEY = os.environ["API_KEY"]
SECRET_KEY = os.environ["SECRET_KEY"]
BASE_URL = "https://api.example.com"
def api_request(method, path, body_obj=None):
body = json.dumps(body_obj, separators=(",", ":")) if body_obj else ""
timestamp = str(int(time.time()))
nonce = secrets.token_hex(16)
body_hash = hashlib.sha256(body.encode()).hexdigest()
string_to_sign = "\n".join([method.upper(), path, timestamp, nonce, body_hash])
signature = hmac.new(SECRET_KEY.encode(), string_to_sign.encode(), hashlib.sha256).hexdigest()
headers = {
"Content-Type": "application/json",
"X-API-Key": API_KEY,
"X-Timestamp": timestamp,
"X-Nonce": nonce,
"X-Signature": signature,
}
return requests.request(method, BASE_URL + path, headers=headers, data=body or None).json()
# Usage
print(api_request("POST", "/v2/topconnect/api/generate_checkout_hash", {"amount": 1500, "currency": "PESO"}))
Rules enforced by the server
X-Timestamp differs from server time by more than 5 minutes are rejected. Keep your server clock synced (NTP).429.All authentication errors return HTTP 401 (or 429 for rate limits) with a JSON body:
{
"error": {
"code": "invalid_signature",
"message": "The request signature does not match."
}
}
| HTTP | code | Meaning | How to fix |
|---|---|---|---|
| 401 | missing_headers | One or more auth headers are missing | Send all four X- headers |
| 401 | invalid_api_key | API key not found or revoked | Check the key in your dashboard |
| 401 | timestamp_expired | Timestamp outside the 5-minute window | Use current Unix seconds; sync your clock |
| 401 | nonce_reused | Nonce was already used | Generate a new random nonce for every request |
| 401 | invalid_signature | Signature does not match | Compare your string-to-sign with the worked example |
| 403 | ip_not_allowed | Request came from an IP not on the key's allowlist | Add your server IP in the dashboard |
| 403 | insufficient_scope | Key doesn't have permission for this endpoint | Use a key with the required scope |
| 429 | rate_limited | Too many requests | Retry after the Retry-After header value |
invalid_signature\n (LF), not \r\n, and no trailing newline.2b2b6750...0f58c1, the bug is in your signing code.Store keys in environment variables or a secrets manager, never in source code or git.
Never call this API directly from a browser or mobile app. Call it from your backend.
Rotate your Secret Key from the dashboard if you suspect it has leaked. The old key keeps working for 24 hours so you can switch without downtime.
Restrict each key to your server IPs and to only the scopes it needs.