Developer Documentation

<p><strong>The Kalkhan API</strong> lets you run license verification, activation and dealer integration from your own systems. This document describes the request/response structure of each endpoint, the authentication method and the error codes.</p> <h2 id="genel-bakis" style="scroll-margin-top:120px;">Overview</h2> <p>The Kalkhan API is a resource-oriented <strong>REST</strong> interface. All request and response bodies are transferred as <strong>JSON</strong> with <strong>UTF-8</strong> character encoding. Connections must be encrypted with <strong>TLS 1.2 or above</strong>; unencrypted (HTTP) requests are rejected.</p> <div class="table-responsive"> <table class="table" style="background:#ffffff;"> <tbody> <tr><th scope="row" style="width:32%;">Base URL</th><td><code>https://api.kalkhan.com.tr/v1</code></td></tr> <tr><th scope="row">Protocol</th><td>HTTPS / TLS 1.2+ (required)</td></tr> <tr><th scope="row">Data format</th><td>JSON · <code>Content-Type: application/json; charset=utf-8</code></td></tr> <tr><th scope="row">Character encoding</th><td>UTF-8</td></tr> <tr><th scope="row">Date format</th><td>ISO 8601 (UTC), e.g. <code>2027-07-23T00:00:00Z</code></td></tr> <tr><th scope="row">Rate limit</th><td>120 requests per minute (per key)</td></tr> </tbody> </table> </div> <p>Every endpoint path is appended to the base URL. The full address of the license verification endpoint, for example, is <code>https://api.kalkhan.com.tr/v1/licenses/verify</code>.</p> <h2 id="kurulum" style="scroll-margin-top:120px;">Setup &amp; Getting Started</h2> <p>If you are starting the integration from scratch, follow the steps below in order. The whole process takes around <strong>15 minutes</strong> and requires no Kalkhan software on your server — an environment that can send HTTPS requests is all you need.</p> <h3 style="margin-top:26px;">Prerequisites</h3> <ul> <li>An approved <strong>Kalkhan dealer account</strong> (apply through the <a href="/iletisim">contact form</a>)</li> <li>A server environment supporting TLS 1.2 or above (PHP 7.4+, Node.js 16+, .NET 6+, Python 3.8+ and similar)</li> <li>A configuration method that keeps the API key <em>on the server side</em> (environment variables recommended)</li> </ul> <h3 style="margin-top:26px;">Step-by-step setup</h3> <ol> <li style="margin-bottom:10px;"><strong>Sign in to your dealer panel.</strong> Panel access opens once your account has passed administrator approval.</li> <li style="margin-bottom:10px;"><strong>Generate an API key.</strong> In the panel&rsquo;s &ldquo;API Keys&rdquo; section, start by creating a test key prefixed with <code>klk_test_</code>.</li> <li style="margin-bottom:10px;"><strong>Define your environment variables.</strong> Add the key and the base URL to your application configuration (example below).</li> <li style="margin-bottom:10px;"><strong>Send your first request.</strong> Call the health endpoint to confirm the connection.</li> <li style="margin-bottom:10px;"><strong>Register your webhook address.</strong> Define your HTTPS webhook address in the panel to receive license events in real time.</li> <li><strong>Go live.</strong> Once testing is complete, switch to the <code>klk_live_</code> key; nothing in your code changes but the key itself.</li> </ol> <h4 style="margin-top:22px;">1) Environment configuration</h4> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;"># .env KALKHAN_API_URL=https://api.kalkhan.com.tr/v1 KALKHAN_API_KEY=klk_test_xxxxxxxxxxxxxxxx KALKHAN_WEBHOOK_SECRET=whsec_xxxxxxxxxxxx</code></pre> <h4 style="margin-top:22px;">2) Connection test</h4> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">curl -s https://api.kalkhan.com.tr/v1/ping \ -H "Authorization: Bearer $KALKHAN_API_KEY" # Successful response { "ok": true, "env": "test", "dealer": "BYI-1001" }</code></pre> <div style="background:#eef3ff;border-left:4px solid #3F82FB;padding:16px 18px;border-radius:8px;margin:22px 0;"> <strong>Tip:</strong> Licenses created in the test environment are never billed and can be deleted at any time. Before taking the integration live, run through the full license creation, verification and cancellation flows with your test key. </div> <h2 id="kimlik-dogrulama" style="scroll-margin-top:120px;">Authentication</h2> <p>API requests are authorized with the <strong>Bearer</strong> scheme using your API key. The key must be sent in the <code>Authorization</code> header of every request.</p> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">Authorization: Bearer klk_live_xxx Content-Type: application/json; charset=utf-8 Accept: application/json</code></pre> <p>You can create and revoke API keys in the &ldquo;API Keys&rdquo; section of <strong>your dealer panel</strong>. Keys prefixed with <code>klk_test_</code> are used for the test environment, and <code>klk_live_</code> for production.</p> <p><strong>Security note:</strong> the API key is a secret credential — keep it on the server side only. Never embed it in a browser, a mobile app bundle or a public code repository. If you suspect a key has leaked, revoke it in the panel immediately and generate a new one.</p> <h2 id="lisans-dogrulama" style="scroll-margin-top:120px;">License Verification</h2> <p>Used to query the validity, product and remaining term of a license key. License keys follow the <code>KLKN-XXXX-XXXX-XXXX-XXXX</code> format.</p> <div class="table-responsive"> <table class="table" style="background:#ffffff;"> <thead> <tr><th>Method</th><th>Endpoint</th><th>Authorization</th></tr> </thead> <tbody> <tr><td><strong>POST</strong></td><td><code>/licenses/verify</code></td><td>Bearer API key</td></tr> </tbody> </table> </div> <h3>Request</h3> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">POST /v1/licenses/verify HTTP/1.1 Host: api.kalkhan.com.tr Authorization: Bearer klk_live_xxx Content-Type: application/json; charset=utf-8 { "key": "KLKN-4F2A-9C71-B3D8-A15E", "deviceId": "WIN-8f2c41ab9d", "productId": "kalkhan-av-windows" }</code></pre> <h3>Response (200 OK)</h3> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">{ "key": "KLKN-4F2A-9C71-B3D8-A15E", "status": "active", "product": "Kalkhan Anti-Virüs (Windows)", "edition": "business", "seats": 25, "seatsUsed": 11, "expiresAt": "2027-03-14T23:59:59Z" }</code></pre> <div class="table-responsive"> <table class="table" style="background:#ffffff;"> <thead> <tr><th style="width:24%;">Field</th><th style="width:18%;">Type</th><th>Description</th></tr> </thead> <tbody> <tr><td><code>status</code></td><td>string</td><td><code>active</code>, <code>expired</code>, <code>suspended</code> or <code>cancelled</code></td></tr> <tr><td><code>product</code></td><td>string</td><td>Display name of the product the license belongs to</td></tr> <tr><td><code>edition</code></td><td>string</td><td><code>home</code>, <code>business</code> or <code>enterprise</code></td></tr> <tr><td><code>seats</code></td><td>integer</td><td>Total number of devices covered by the license</td></tr> <tr><td><code>expiresAt</code></td><td>string</td><td>Expiry date in ISO 8601 format (UTC)</td></tr> </tbody> </table> </div> <p>An unknown key returns <code>404 not_found</code>, and a malformed key returns <code>400 invalid_request</code>.</p> <h2 id="lisans-olusturma" style="scroll-margin-top:120px;">License Creation (Dealer)</h2> <p>Used by authorized dealers to issue a new license on behalf of an end customer. This endpoint can only be called with API keys that carry dealer permissions.</p> <div class="table-responsive"> <table class="table" style="background:#ffffff;"> <thead> <tr><th>Method</th><th>Endpoint</th><th>Authorization</th></tr> </thead> <tbody> <tr><td><strong>POST</strong></td><td><code>/licenses</code></td><td>Dealer API key</td></tr> </tbody> </table> </div> <h3>Request</h3> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">POST /v1/licenses HTTP/1.1 Host: api.kalkhan.com.tr Authorization: Bearer klk_live_xxx Content-Type: application/json; charset=utf-8 { "productId": "kalkhan-av-windows", "edition": "business", "seats": 25, "customer": { "name": "Örnek Bilişim A.Ş.", "email": "muhasebe@ornekbilisim.com.tr", "taxId": "1234567890", "taxOffice": "Ümraniye", "address": "Mithatpaşa Cd. No:116, Ümraniye/İstanbul" } }</code></pre> <h3>Response (201 Created)</h3> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">{ "key": "KLKN-7B10-CE45-2F93-D8A6", "status": "active", "product": "Kalkhan Anti-Virüs (Windows)", "edition": "business", "seats": 25, "amount": 34750.00, "currency": "TRY", "expiresAt": "2027-07-23T23:59:59Z" }</code></pre> <p><strong>For corporate sales</strong> (when <code>edition</code> is <code>business</code> or <code>enterprise</code>), the fields <code>customer.taxId</code> (tax identification number), <code>customer.taxOffice</code> (tax office) and <code>customer.address</code> are <strong>mandatory</strong> so that an invoice can be issued. Requests missing these fields are rejected with <code>400 invalid_request</code>. For individual (<code>home</code>) licenses the tax fields are optional.</p> <p>Repeated requests for the same customer and product within a short window are answered with <code>409 duplicate</code>; adding an <code>Idempotency-Key</code> header is recommended to prevent duplicate licenses.</p> <h2 id="bayi-api" style="scroll-margin-top:120px;">Dealer API</h2> <p>Two endpoints let dealers query their own license portfolio and sales summary.</p> <div class="table-responsive"> <table class="table" style="background:#ffffff;"> <thead> <tr><th style="width:14%;">Method</th><th style="width:32%;">Endpoint</th><th>Description</th></tr> </thead> <tbody> <tr><td><strong>GET</strong></td><td><code>/dealer/licenses</code></td><td>Paginated list of the dealer&rsquo;s licenses</td></tr> <tr><td><strong>GET</strong></td><td><code>/dealer/stats</code></td><td>Sales count, revenue and upcoming renewal summary</td></tr> </tbody> </table> </div> <h3>Pagination parameters</h3> <div class="table-responsive"> <table class="table" style="background:#ffffff;"> <thead> <tr><th style="width:18%;">Parameter</th><th style="width:16%;">Default</th><th>Description</th></tr> </thead> <tbody> <tr><td><code>page</code></td><td>1</td><td>Page number to display (starts at 1)</td></tr> <tr><td><code>limit</code></td><td>25</td><td>Records per page (100 at most)</td></tr> <tr><td><code>status</code></td><td>&mdash;</td><td>Optional filter: <code>active</code>, <code>expired</code>, <code>cancelled</code></td></tr> </tbody> </table> </div> <h3>Request and response</h3> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">GET /v1/dealer/licenses?page=2&amp;limit=25&amp;status=active HTTP/1.1 Host: api.kalkhan.com.tr Authorization: Bearer klk_live_xxx { "data": [ { "key": "KLKN-4F2A-9C71-B3D8-A15E", "status": "active", "product": "Kalkhan Anti-Virüs (Windows)", "edition": "business", "seats": 25, "customer": "Örnek Bilişim A.Ş.", "expiresAt": "2027-03-14T23:59:59Z" } ], "pagination": { "page": 2, "limit": 25, "total": 143, "totalPages": 6 } }</code></pre> <p>The <code>GET /dealer/stats</code> endpoint returns a periodic summary: total active licenses, monthly sales count, total revenue and the number of licenses expiring in the next 30 days.</p> <h2 id="webhooks" style="scroll-margin-top:120px;">Webhooks</h2> <p>Use webhooks to push license lifecycle changes to your systems in real time. You define the target address in the dealer panel, and Kalkhan sends a JSON body to that address with a <code>POST</code> request.</p> <div class="table-responsive"> <table class="table" style="background:#ffffff;"> <thead> <tr><th style="width:34%;">Event</th><th>When is it triggered?</th></tr> </thead> <tbody> <tr><td><code>license.created</code></td><td>When a new license is created</td></tr> <tr><td><code>license.expired</code></td><td>When a license reaches the end of its term</td></tr> <tr><td><code>license.cancelled</code></td><td>When a license is cancelled or a refund is processed</td></tr> </tbody> </table> </div> <h3>Example event body</h3> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">POST /webhooks/kalkhan HTTP/1.1 X-Kalkhan-Signature: t=1784937600,v1=9f8c1d2b7e4a63f05c8b1a9d4e7f2c30b6a5d8e1f4c7b0a3d6e9f2c5b8a1d4e7 Content-Type: application/json; charset=utf-8 { "id": "evt_01J9X2K7QF", "event": "license.created", "createdAt": "2026-07-23T09:41:02Z", "data": { "key": "KLKN-7B10-CE45-2F93-D8A6", "product": "Kalkhan Anti-Virüs (Windows)", "edition": "business", "seats": 25, "expiresAt": "2027-07-23T23:59:59Z" } }</code></pre> <h3>Signature verification</h3> <p>Every webhook request is signed with the <code>X-Kalkhan-Signature</code> header. The signature is an <strong>HMAC-SHA256</strong> digest of the timestamp and the raw request body, computed with your webhook secret. Always verify the signature before processing a request, and reject timestamps older than 5 minutes.</p> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">// Node.js — signature verification const crypto = require('crypto'); function verify(rawBody, header, secret) { const parts = Object.fromEntries( header.split(',').map(p =&gt; p.split('=')) ); const expected = crypto .createHmac('sha256', secret) .update(parts.t + '.' + rawBody) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(expected), Buffer.from(parts.v1) ); }</code></pre> <p>If your server does not return a <code>2xx</code> response, the event is retried up to 5 times at increasing intervals (1 min, 5 min, 30 min, 2 h, 12 h). Assume the same event may arrive more than once and de-duplicate on the <code>id</code> field.</p> <h2 id="hata-kodlari" style="scroll-margin-top:120px;">Error Codes</h2> <p>Errors are returned with standard HTTP status codes and carry a machine-readable <code>code</code> together with a descriptive <code>message</code> in the body.</p> <div class="table-responsive"> <table class="table" style="background:#ffffff;"> <thead> <tr><th style="width:14%;">HTTP</th><th style="width:26%;">Code</th><th>Description</th></tr> </thead> <tbody> <tr><td><strong>400</strong></td><td><code>invalid_request</code></td><td>A missing or malformed parameter was sent</td></tr> <tr><td><strong>401</strong></td><td><code>unauthorized</code></td><td>API key missing, invalid or revoked</td></tr> <tr><td><strong>403</strong></td><td><code>forbidden</code></td><td>The key is not authorized for this endpoint</td></tr> <tr><td><strong>404</strong></td><td><code>not_found</code></td><td>The requested license or resource was not found</td></tr> <tr><td><strong>409</strong></td><td><code>duplicate</code></td><td>The record already exists; the duplicate request was blocked</td></tr> <tr><td><strong>429</strong></td><td><code>rate_limited</code></td><td>Rate limit exceeded; wait for the <code>Retry-After</code> header</td></tr> <tr><td><strong>500</strong></td><td><code>server_error</code></td><td>An unexpected error occurred on the server side</td></tr> </tbody> </table> </div> <h3>Example error response</h3> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">HTTP/1.1 400 Bad Request Content-Type: application/json; charset=utf-8 { "error": { "code": "invalid_request", "message": "The customer.taxId field is required for corporate licenses.", "field": "customer.taxId", "requestId": "req_01J9X2K7QF" } }</code></pre> <p>Quoting the <code>requestId</code> from the response when you open a support ticket helps us investigate quickly.</p> <h2 id="sdk" style="scroll-margin-top:120px;">SDK &amp; Examples</h2> <p>Official SDKs are in preparation. In the meantime you can call the API directly over HTTP. Below are the cURL and JavaScript equivalents of a license verification request.</p> <h3>cURL</h3> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">curl -X POST https://api.kalkhan.com.tr/v1/licenses/verify \ -H "Authorization: Bearer klk_live_xxx" \ -H "Content-Type: application/json; charset=utf-8" \ -d '{ "key": "KLKN-4F2A-9C71-B3D8-A15E", "deviceId": "WIN-8f2c41ab9d", "productId": "kalkhan-av-windows" }'</code></pre> <h3>JavaScript (fetch)</h3> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">const API_KEY = process.env.KALKHAN_API_KEY; // keep this on the server side async function verifyLicense(key, deviceId) { const res = await fetch( 'https://api.kalkhan.com.tr/v1/licenses/verify', { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json; charset=utf-8' }, body: JSON.stringify({ key, deviceId, productId: 'kalkhan-av-windows' }) } ); if (!res.ok) { const { error } = await res.json(); throw new Error(`${error.code}: ${error.message}`); } return res.json(); // { status, product, edition, seats, expiresAt } } verifyLicense('KLKN-4F2A-9C71-B3D8-A15E', 'WIN-8f2c41ab9d') .then(license =&gt; console.log(license.status, license.expiresAt)) .catch(err =&gt; console.error('Verification failed:', err.message));</code></pre> <p>These examples are for server-side use only. Do not place the API key in code that runs in a browser; route verification requests from your client applications through your own backend service.</p> <!-- Warning Box Start --> <div role="note" style="margin-top:40px;background:#FFF8E6;border:1px solid #F0D28A;border-left:5px solid #C9A227;border-radius:12px;padding:22px 24px;display:flex;gap:16px;align-items:flex-start;"> <span style="flex:0 0 auto;color:#C9A227;font-size:22px;line-height:1.2;"><i class="fa-solid fa-triangle-exclamation" aria-hidden="true"></i></span> <span style="color:#4a3d12;font-size:15px;line-height:1.7;"> <strong style="display:block;color:#0F2453;font-size:16px;margin-bottom:4px;">Closed beta</strong> The API is currently in closed beta; request access at <a href="mailto:bilgi@kalkhan.com.tr" style="color:#0F2453;font-weight:600;">bilgi@kalkhan.com.tr</a>. Backwards-incompatible endpoint changes may occur while the beta is running. </span> </div> <!-- Warning Box End -->