Growzy

API Documentation

Quick start

  1. Register and verify your email.
  2. Create an API key in Developer → API Keys (secret shown once).
  3. Optionally register a webhook endpoint.
  4. Create a deposit and redirect your customer to checkout_url.
  5. Receive the webhook, verify the signature and fulfill your order.

All API requests go to /api/v1/… on this domain. Use Idempotency-Key on all financial POSTs.

Authentication (HMAC-SHA256)

Send these headers on every request:

X-GROWZY-KEY:       your key id (api_...)
X-GROWZY-TIMESTAMP: unix seconds
X-GROWZY-NONCE:     random string (unique per request)
X-GROWZY-SIGNATURE: HMAC-SHA256 hex of the canonical string

The canonical signing string is (each field on its own line):

API_KEY
TIMESTAMP
NONCE
HTTP_METHOD
REQUEST_PATH
SHA256_HEX(raw_request_body)

Requests with timestamps outside the tolerance window (default 300s) or with reused nonces are rejected. Signatures are compared in constant time.

API_KEY="YOUR_API_KEY"
API_SECRET="YOUR_API_SECRET"
TS=$(date +%s)
NONCE=$(head -c16 /dev/urandom | xxd -p)
BODY='{"amount":"500.00"}'
BODY_HASH=$(printf '%s' "$BODY" | sha256sum | cut -d' ' -f1)
SIG=$(printf 'API_KEY
%s
%s
%s
%s
%s' "$TS" "$NONCE" "POST" "/api/v1/deposits" "$BODY_HASH" | openssl dgst -sha256 -hmac "$API_SECRET" -hex | sed 's/^.* //')
curl -X POST https://wallet.growzylabs.in/api/v1/deposits \
  -H "Content-Type: application/json" \
  -H "X-GROWZY-KEY: $API_KEY" \
  -H "X-GROWZY-TIMESTAMP: $TS" \
  -H "X-GROWZY-NONCE: $NONCE" \
  -H "X-GROWZY-SIGNATURE: $SIG" \
  -H "Idempotency-Key: order-1234" \
  -d "$BODY"
<?php
$apiKey = 'YOUR_API_KEY';
$secret  = 'YOUR_API_SECRET';
$ts      = time();
$nonce   = bin2hex(random_bytes(16));
$body    = json_encode(['amount' => '500.00']);
$method  = 'POST';
$path    = '/api/v1/deposits';
$canonical = $apiKey."\n".$ts."\n".$nonce."\n".$method."\n".$path."\n".hash('sha256', $body);
$sig = hash_hmac('sha256', $canonical, $secret);
$ch = curl_init('https://wallet.growzylabs.in'.$path);
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_POSTFIELDS => $body,
  CURLOPT_HTTPHEADER => [
    'Content-Type: application/json',
    'X-GROWZY-KEY: '.$apiKey,
    'X-GROWZY-TIMESTAMP: '.$ts,
    'X-GROWZY-NONCE: '.$nonce,
    'X-GROWZY-SIGNATURE: '.$sig,
    'Idempotency-Key: order-1234',
  ],
  CURLOPT_RETURNTRANSFER => true,
]);
print_r(json_decode(curl_exec($ch), true));
import hashlib, hmac, json, os, time, requests

api_key = 'YOUR_API_KEY'
secret  = 'YOUR_API_SECRET'
ts      = str(int(time.time()))
nonce   = os.urandom(16).hex()
body    = json.dumps({'amount': '500.00'})
path    = '/api/v1/deposits'
canonical = '\n'.join([api_key, ts, nonce, 'POST', path, hashlib.sha256(body.encode()).hexdigest()])
sig = hmac.new(secret.encode(), canonical.encode(), hashlib.sha256).hexdigest()
r = requests.post('https://wallet.growzylabs.in' + path, data=body, headers={
    'Content-Type': 'application/json',
    'X-GROWZY-KEY': api_key,
    'X-GROWZY-TIMESTAMP': ts,
    'X-GROWZY-NONCE': nonce,
    'X-GROWZY-SIGNATURE': sig,
    'Idempotency-Key': 'order-1234',
})
print(r.json())
const crypto = require('crypto');
const apiKey = 'YOUR_API_KEY';
const secret  = 'YOUR_API_SECRET';
const ts = Math.floor(Date.now() / 1000).toString();
const nonce = crypto.randomBytes(16).toString('hex');
const body = JSON.stringify({ amount: '500.00' });
const path = '/api/v1/deposits';
const canonical = [apiKey, ts, nonce, 'POST', path,
  crypto.createHash('sha256').update(body).digest('hex')].join('\n');
const sig = crypto.createHmac('sha256', secret).update(canonical).digest('hex');
fetch('https://wallet.growzylabs.in' + path, {
  method: 'POST', body,
  headers: {
    'Content-Type': 'application/json',
    'X-GROWZY-KEY': apiKey, 'X-GROWZY-TIMESTAMP': ts,
    'X-GROWZY-NONCE': nonce, 'X-GROWZY-SIGNATURE': sig,
    'Idempotency-Key': 'order-1234',
  },
}).then(r => r.json()).then(console.log);

Deposits

POST /api/v1/deposits          { "amount": "500.00" }
GET  /api/v1/deposits           ?page=1&limit=20
GET  /api/v1/deposits/{id}
POST /api/v1/deposits/{id}/utr  { "utr": "123456789012" }

Scopes: deposits:create, deposits:read. Test-environment keys return a sandbox response and never touch real balances.

Wallet

GET /api/v1/wallet
GET /api/v1/wallet/balance

Scope: wallet:read.

Withdrawals

POST /api/v1/withdrawals          { "amount": "500.00", "destination": "name@bank" }
GET  /api/v1/withdrawals           ?page=1
GET  /api/v1/withdrawals/{id}

Scopes: withdrawals:create, withdrawals:read. Fees are computed server-side from platform settings.

Transactions

GET /api/v1/transactions           ?type=deposit&status=completed
GET /api/v1/transactions/{id}

Scope: transactions:read.

Webhooks

POST   /api/v1/webhooks        { "url": "https://…", "events": ["deposit.approved"] }
GET    /api/v1/webhooks
DELETE /api/v1/webhooks/{id}

Delivered events carry headers X-Growzy-Event, X-Growzy-Event-Id and X-Growzy-Signature: t=<ts>,v1=<hmac>. The HMAC is computed over <timestamp>.<payload> with your endpoint secret. Verify the signature and the timestamp before processing. Failed deliveries retry up to 5 times with exponential backoff. Every event has a unique event_id for safe de-duplication.

<?php
$payload = file_get_contents('php://input');
$header  = $_SERVER['HTTP_X_GROWZY_SIGNATURE'] ?? '';
[$t, $v1] = [null, null];
foreach (explode(',', $header) as $part) {
    [$k, $v] = array_pad(explode('=', trim($part), 2), 2, null);
    if ($k === 't') $t = $v; if ($k === 'v1') $v1 = $v;
}
$expected = hash_hmac('sha256', $t.'.'.$payload, 'YOUR_WEBHOOK_SECRET');
if (!hash_equals($expected, $v1 ?? '') || abs(time() - (int)$t) > 300) {
    http_response_code(400); exit;
}
$event = json_decode($payload, true);
// de-duplicate on $event['event_id'], then fulfill order

Idempotency

Send Idempotency-Key: <unique-key> on every financial POST. If the same key is submitted again, the original response is returned (with header Idempotent-Replay: true) and no duplicate transaction is created.

Errors & status codes

200 OK              success
201 Created         resource created
400 INVALID_REQUEST malformed request
401 UNAUTHORIZED / INVALID_SIGNATURE
403 FORBIDDEN        missing scope or permission
404 NOT_FOUND
422 VALIDATION_ERROR / INSUFFICIENT_BALANCE
429 RATE_LIMITED
500 INTERNAL_ERROR

Error responses use {"status":"error","error":{"code":…,"message":…},"meta":{"request_id":…}}. Never expose your secrets in support tickets — share the request_id instead.

Telegram integration

Connect a bot under Telegram in your dashboard. Customers send /pay 500 to your bot and receive a Growzy checkout link. Your bot can also call the API above to create sessions programmatically — see docs/TELEGRAM_EXAMPLES.md in the deployment package for PHP, Python and Node examples using YOUR_BOT_TOKEN.