CareerByteCode

API guide for partners

For developers at CareerByteCode API partners. Your buyer always pays on CareerByteCode's own secure checkout; your server lists programs, makes checkout links and follows the orders.

KeysSigningPingCatalogueCheckoutOrdersSandboxNotificationsErrors

Keys, modes and addresses

Signing every request

Send four headers. The signature is HMAC-SHA256 with your secret over five lines joined by a newline:

X-CBC-Key:       cbc_test_xxxxxxxxxxxx
X-CBC-Timestamp: 1791360000          unix seconds, within 5 minutes of our clock
X-CBC-Nonce:     6f1c2b9e0a7d4c11     new for every request, 8-64 of A-Z a-z 0-9 _ -
X-CBC-Signature: hex HMAC_SHA256(secret,
                   METHOD + "\n" + PATH_AND_QUERY + "\n" + TIMESTAMP + "\n" + NONCE + "\n" + sha256_hex(BODY))

PATH_AND_QUERY is exactly what you request, for example /api/v1/catalogue?type=course. BODY is the raw body you send (empty for GET).

PHP

function cbc_call(string $method, string $pathQuery, string $body, string $key, string $secret): array {
    $ts = (string)time(); $nonce = bin2hex(random_bytes(8));
    $sts = $method . "\n" . $pathQuery . "\n" . $ts . "\n" . $nonce . "\n" . hash('sha256', $body);
    $ch = curl_init('https://www.careerbytecode.in' . $pathQuery);
    curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_RETURNTRANSFER => true, CURLOPT_POSTFIELDS => $body !== '' ? $body : null,
        CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'X-CBC-Key: ' . $key, 'X-CBC-Timestamp: ' . $ts, 'X-CBC-Nonce: ' . $nonce,
                               'X-CBC-Signature: ' . hash_hmac('sha256', $sts, $secret)]]);
    $res = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
    return [$code, json_decode((string)$res, true)];
}

Node.js

const crypto = require('crypto');
async function cbcCall(method, pathQuery, body, key, secret) {
  const ts = String(Math.floor(Date.now() / 1000)), nonce = crypto.randomBytes(8).toString('hex');
  const sts = [method, pathQuery, ts, nonce, crypto.createHash('sha256').update(body).digest('hex')].join('\n');
  const sig = crypto.createHmac('sha256', secret).update(sts).digest('hex');
  const r = await fetch('https://www.careerbytecode.in' + pathQuery, { method, body: body || undefined,
    headers: { 'Content-Type': 'application/json', 'X-CBC-Key': key, 'X-CBC-Timestamp': ts, 'X-CBC-Nonce': nonce, 'X-CBC-Signature': sig } });
  return [r.status, await r.json()];
}

GET /api/v1/ping

Checks your key, signature and server address. Answers your partner name, mode, status, scopes, products, the IP we saw and our time.

GET /api/v1/catalogue

The programs you may sell right now: what your ByteStore resells, limited to the product types agreed with you. Optional ?type=course|exam|byteclass|byteplay|bytelabs.

{"ok":true,"data":{"currency":"INR","gst_pct":18,"items":[
  {"id":"course:kubernetes-bootcamp","type":"course","ref":"kubernetes-bootcamp","title":"Kubernetes Bootcamp",
   "base_inr":9999,"gst_inr":1800,"total_inr":11799}]},"request_id":"req_..."}

Show the total including GST. Prices change only on our side; read the catalogue at least once a day.

POST /api/v1/checkout

Makes an order and returns the checkout link to send your buyer to. Body: {"item":"course:kubernetes-bootcamp","partner_ref":"A-1001"}. partner_ref is your own order number (up to 64 of letters, digits, . : - _).

Send X-CBC-Idempotency-Key (8-80 characters, unique per order on your side): if your request is retried, you get the same order back instead of a second one.

201 {"ok":true,"data":{"order":{"id":"9f2c...","status":"created","mode":"live","item":"course:kubernetes-bootcamp",
  "title":"Kubernetes Bootcamp","base_inr":9999,"gst_inr":1800,"total_inr":11799,"partner_ref":"A-1001",
  "checkout_url":"https://www.careerbytecode.in/api/go.php?t=9f2c...","created_at":1791360000,"expires_at":1791532800}}}

Redirect the buyer to checkout_url within 48 hours. They sign in with Google on CareerByteCode and pay there. You never handle card or UPI details.

GET /api/v1/orders and /api/v1/orders/{id}

Your orders for the key's mode, newest first. Optional ?status=created|paid|refunded|expired and &limit=1-200. A single order by its id. Statuses: created (link not yet paid), paid, refunded (full refund), expired (not paid within 48 hours).

Sandbox

With a test key every order is a sandbox order. Its checkout link opens a test page with "Simulate a successful payment" and "Simulate a refund". Your server receives the same notifications as for a real order. Nothing is charged or booked.

Notifications to your server (webhooks)

Set your https address in your console. We POST JSON for order.paid, order.refunded and webhook.test:

POST https://your-site.example/cbc-webhook
X-CBC-Event: order.paid
X-CBC-Delivery: 1234
X-CBC-Timestamp: 1791360420
X-CBC-Signature: hex HMAC_SHA256(webhook_secret, TIMESTAMP + "." + RAW_BODY)

{"id":"evt_...","event":"order.paid","created":1791360420,"data":{"order":{ ...same as GET /orders/{id}... }}}

Check the signature with your webhook secret (it starts with whsec_), refuse timestamps older than 5 minutes, then answer any 2xx within 6 seconds. Use data.order.id to ignore a notification you already handled. If we do not get a 2xx we retry after 5 minutes, 15 minutes, 1, 3, 12 and 24 hours.

// PHP
$raw = file_get_contents('php://input'); $ts = $_SERVER['HTTP_X_CBC_TIMESTAMP'] ?? '';
$ok = abs(time() - (int)$ts) <= 300 && hash_equals(hash_hmac('sha256', $ts . '.' . $raw, $webhookSecret), $_SERVER['HTTP_X_CBC_SIGNATURE'] ?? '');

Errors and limits

Every error answers {"ok":false,"error":{"code":"...","message":"..."},"request_id":"req_..."}. Quote the request id when you contact us.

StatusCodes
400key_in_url, bad_json, unknown_item, bad_partner_ref, bad_type, bad_status, bad_idempotency_key
401missing_key, unknown_key, key_revoked, key_expired, bad_timestamp, bad_nonce, bad_signature, nonce_reused
403https_required, partner_paused, partner_not_active, ip_not_allowed, scope_missing
404not_found, unknown_order
409idempotency_conflict
429rate_limited (see Retry-After; the limit per key is agreed with you, 60 a minute by default)
503rate_unavailable (try again shortly)

Keys can be rotated: the new key works at once and the old one keeps working for 7 days.