Topconnect API Docs V2
    • Topconnect API
      • Generate Hash for WebPay
        POST
      • Generate RAW QRPH IMAGE
        POST
      • Generate RAW QRPH String
        POST
      • QRPH Cashout
        POST

    Topconnect API

    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!

    Authentication

    All API requests must be signed with your API Key and Secret Key using HMAC-SHA256.

    • Your API Key (ak_...) identifies your account and is sent with every request.
    • Your Secret Key (sk_...) is used to compute the signature. Never send it in a request, and never put it in browser or mobile app code.
      All requests must use HTTPS. Requests over plain HTTP are rejected.

    Required headers

    HeaderDescriptionExample
    X-API-KeyYour API keyak_test_51HxQ2EXAMPLEKEY
    X-TimestampCurrent Unix time in seconds (UTC)1760610000
    X-NonceRandom, unique string per request (16+ bytes, hex). Never reuse.7c2f9e1a4b8d3c6e5f0a1b2c3d4e5f60
    X-SignatureLowercase hex HMAC-SHA256 signature (see below)2b2b6750...0f58c1
    Content-Typeapplication/json for requests with a bodyapplication/json

    Building the signature

    Step 1: Hash the request body

    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.

    Step 2: Build the string to sign

    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)
    

    Step 3: Sign it

    signature = lowercase_hex( HMAC_SHA256( key = SECRET_KEY, message = string_to_sign ) )
    

    Send the result in the X-Signature header.


    Worked example

    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
    

    Example 1: POST with a body

    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"
    }
    '
    

    Code samples

    Node.js

    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);
    

    Python

    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

    • Timestamp window: requests whose X-Timestamp differs from server time by more than 5 minutes are rejected. Keep your server clock synced (NTP).
    • Nonce reuse: each nonce can be used only once per API key. A repeated nonce is rejected as a replay.
    • Signature mismatch: any change to the method, path, query string, or body after signing invalidates the request.
    • Rate limits: each API key is limited to N requests per minute. Exceeding it returns 429.

    Error responses

    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."
      }
    }
    
    HTTPcodeMeaningHow to fix
    401missing_headersOne or more auth headers are missingSend all four X- headers
    401invalid_api_keyAPI key not found or revokedCheck the key in your dashboard
    401timestamp_expiredTimestamp outside the 5-minute windowUse current Unix seconds; sync your clock
    401nonce_reusedNonce was already usedGenerate a new random nonce for every request
    401invalid_signatureSignature does not matchCompare your string-to-sign with the worked example
    403ip_not_allowedRequest came from an IP not on the key's allowlistAdd your server IP in the dashboard
    403insufficient_scopeKey doesn't have permission for this endpointUse a key with the required scope
    429rate_limitedToo many requestsRetry after the Retry-After header value

    Troubleshooting invalid_signature

    1. Body mismatch: you signed one JSON string but sent another (different spacing or key order). Sign and send the same string.
    2. Path mismatch: the path must include the query string exactly as sent, and must not include the domain.
    3. Newlines: use \n (LF), not \r\n, and no trailing newline.
    4. Hex case: both the body hash and signature must be lowercase hex.
    5. Wrong key: HMAC must use the Secret Key, not the API Key.
    6. Run the worked example above through your code. If you don't get 2b2b6750...0f58c1, the bug is in your signing code.

    Keeping your keys safe

    • 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.

    Modified at 2026-10-17 04:21:50
    Next
    Generate Hash for WebPay
    Built with