CodeBlanks Logo Icon CodeBlanks
Developers

Connect CodeBlanks to your product

Let your users sign in with their CodeBlanks account, read their learning progress (XP, badges, certificates) via the API, and receive events instantly via webhooks.

Sign in to get started

Overview

There are three ways in; use one or all of them:

"Sign in with CodeBlanks": users sign in to your site with their CodeBlanks account (OAuth 2.0 + OpenID Connect).
API: read profile, XP, badge and certificate data as JSON.
Webhooks: your server is notified when a user earns a badge or finishes a course.

Authentication

Send a credential in the Authorization header with every request. Two kinds are accepted:

  • Personal API key (starts with cbk_): created under Dashboard > Developer, reads your own account's data.
  • OAuth access token: obtained via "Sign in with CodeBlanks", reads the consenting user's data within the granted scopes.
curl -H "Authorization: Bearer cbk_…" https://codeblanks.com/api/v1/me

Keep keys and secrets on your server only; never ship them in a browser or a mobile app bundle.

Scopes

A key or token can only call endpoints for the scopes it holds.

  • profile: name, username, avatar, profile link, XP
  • email: email address
  • progress: XP, rank, streak, badges, certificates

Endpoints

All endpoints are read-only (GET) and return JSON. Success responses use the "data" field, errors the "error" field. Use ?lang=tr|en to choose the language of badge and rank names.

https://codeblanks.com/api/v1

Request Scope Description
GET /me profile Profile of the authenticated user. Includes the email when the email scope is granted.
GET /me/progress progress XP, rank, streak, completed course count, badges and certificates.
GET /users/{username} profile Public profile of any user.
GET /users/{username}/badges profile Badges the user has earned.
GET /certificates/{code} any Certificate verification: returns the holder and the course when the code is valid.

Example request

curl -H "Authorization: Bearer cbk_…" "https://codeblanks.com/api/v1/me/progress?lang=tr"

Example response

{
  "data": {
    "xp": 1840,
    "rank": {
      "current": { "id": 3, "name": "Kod Çırağı", "min_xp": 1000 },
      "next": { "id": 4, "name": "Geliştirici", "min_xp": 2500 }
    },
    "streak": { "current": 6, "longest": 21, "last_activity": "2026-10-13" },
    "courses_completed": 2,
    "badges": [
      { "id": 12, "key": "lessons_completed_10", "name": "10 ders", "icon": "fas fa-book", "color": "#0D9488", "xp_reward": 50, "earned_at": "2026-10-02T14:11:09+03:00" }
    ],
    "certificates": [
      { "code": "CB-1A2B-3C4D-5E6F", "course_id": 4, "course_title": "PHP Temelleri", "issued_at": "2026-09-28T10:00:00+03:00", "verify_url": "https://codeblanks.com/api/v1/certificates/CB-1A2B-3C4D-5E6F" }
    ]
  }
}

Rate limits and errors

60 requests per minute per key / token. Every response includes X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers; over the limit you get 429 with Retry-After.

401 Credential missing, invalid, expired or revoked.
403 Insufficient scope (insufficient_scope) or suspended account.
404 User, certificate or endpoint not found.
429 Rate limit exceeded.
503 The API is temporarily disabled.
{ "error": { "code": "insufficient_scope", "message": "This credential does not have the \"progress\" scope." } }

Add "Sign in with CodeBlanks"

Standard OAuth 2.0 authorization code flow + PKCE (S256, required) + OpenID Connect. Any OAuth / OIDC library works by pointing it at the discovery URL.

  1. Create an app under Dashboard > Developer > My apps; copy the client_id and client secret and register your redirect URI.
  2. Send the user to the authorization URL (response_type=code, client_id, redirect_uri, scope, state, code_challenge, code_challenge_method=S256).
  3. After consent, the user returns to your redirect URI with ?code=…&state=…; verify the state.
  4. Exchange the code at the token endpoint from your server (grant_type=authorization_code, code, redirect_uri, code_verifier, client credentials). You get an access_token, refresh_token and id_token.
  5. Verify the id_token with the JWKS, or call userinfo / the API with the access_token. Match the user by "sub" (the CodeBlanks user ID).

Endpoints

Discovery document https://codeblanks.com/.well-known/openid-configuration
Authorization https://codeblanks.com/oauth/authorize
Token POST https://codeblanks.com/oauth/token
User info https://codeblanks.com/oauth/userinfo
Signing keys (JWKS) https://codeblanks.com/oauth/jwks.json
Token revocation POST https://codeblanks.com/oauth/revoke

Access tokens last 1 hour, refresh tokens 30 days. Refresh tokens rotate on every use; if an old one is reused, all tokens of that session are revoked.

PHP example (league/oauth2-client)

$provider = new \League\OAuth2\Client\Provider\GenericProvider([
    'clientId'                => 'cb_…',
    'clientSecret'            => 'cbs_…',
    'redirectUri'             => 'https://liyakat.net/auth/codeblanks/callback',
    'urlAuthorize'            => 'https://codeblanks.com/oauth/authorize',
    'urlAccessToken'          => 'https://codeblanks.com/oauth/token',
    'urlResourceOwnerDetails' => 'https://codeblanks.com/oauth/userinfo',
    'scopes'                  => 'openid profile email progress',
    'scopeSeparator'          => ' ',
    'pkceMethod'              => \League\OAuth2\Client\Provider\GenericProvider::PKCE_METHOD_S256,
]);

// 1) Giriş düğmesi
if (!isset($_GET['code'])) {
    $url = $provider->getAuthorizationUrl();
    $_SESSION['oauth2state'] = $provider->getState();
    $_SESSION['oauth2pkce']  = $provider->getPkceCode();
    header('Location: ' . $url);
    exit;
}

// 2) Dönüş
if (!hash_equals($_SESSION['oauth2state'] ?? '', $_GET['state'] ?? '')) {
    exit('Invalid state');
}
$provider->setPkceCode($_SESSION['oauth2pkce']);
$token = $provider->getAccessToken('authorization_code', ['code' => $_GET['code']]);
$user  = $provider->getResourceOwner($token)->toArray(); // sub, preferred_username, name, email …

// 3) API: XP ve rozetler
$req = $provider->getAuthenticatedRequest('GET', 'https://codeblanks.com/api/v1/me/progress', $token);
$progress = json_decode((string) $provider->getResponse($req)->getBody(), true)['data'];

Webhooks

Choose a URL and events under Dashboard > Developer > Webhooks. A personal webhook receives only your events; a webhook attached to your app receives events for every user who granted your app the "progress" scope.

Events

badge.earned Badge earned progress
course.completed Course completed progress
certificate.issued Certificate issued progress
rank.changed Rank up progress

Headers

POST /webhooks/codeblanks HTTP/1.1
Content-Type: application/json
User-Agent: CodeBlanks-Webhooks/1.0
X-CodeBlanks-Event: badge.earned
X-CodeBlanks-Delivery: 9f0c…
X-CodeBlanks-Signature: t=1791880000,v1=5d41402abc4b2a76b9719d911017c592…

Payload

{
  "id": "evt_9f0c2b7e4d1a4c0b8e6f3a2d1c0b9a87",
  "type": "badge.earned",
  "created": 1791880000,
  "data": {
    "user": { "id": 42, "username": "murat", "profile_url": "https://codeblanks.com/@murat" },
    "badge": { "id": 12, "key": "lessons_completed_10", "name": "10 ders", "icon": "fas fa-book", "xp_reward": 50 }
  }
}

Verify the signature

The X-CodeBlanks-Signature header looks like t=<timestamp>,v1=<signature>. signature = HMAC-SHA256(signing secret, "<t>.<raw body>"). Reject requests older than 5 minutes and don't process the same X-CodeBlanks-Delivery twice.

$body   = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_CODEBLANKS_SIGNATURE'] ?? '';
parse_str(str_replace(',', '&', $header), $sig);           // ['t' => …, 'v1' => …]
$expected = hash_hmac('sha256', ($sig['t'] ?? '') . '.' . $body, getenv('CODEBLANKS_WEBHOOK_SECRET'));

if (!hash_equals($expected, $sig['v1'] ?? '') || abs(time() - (int) ($sig['t'] ?? 0)) > 300) {
    http_response_code(400);
    exit;
}
http_response_code(204);                                   // hemen dön, işi kuyruğa at
$event = json_decode($body, true);
// Node.js (Express): gövdeyi ham haliyle al
app.post('/webhooks/codeblanks', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = Object.fromEntries(req.get('X-CodeBlanks-Signature').split(',').map(p => p.split('=')));
  const expected = crypto.createHmac('sha256', process.env.CODEBLANKS_WEBHOOK_SECRET)
    .update(sig.t + '.' + req.body).digest('hex');
  const a = Buffer.from(expected), b = Buffer.from(sig.v1 || '');
  const ok = a.length === b.length && crypto.timingSafeEqual(a, b);
  if (!ok || Math.abs(Date.now() / 1000 - Number(sig.t)) > 300) return res.sendStatus(400);
  res.sendStatus(204);
  const event = JSON.parse(req.body);
});

A non-2xx response or a timeout (5 s) counts as a failure and is retried: 1 min, 5 min, 30 min, 2 h, 6 h, 24 h. After many consecutive failures the webhook is disabled. Return 2xx right away and do the work in the background.

Support

For questions or bug reports, use the contact page. Contact