Kortilink API

REST API + Webhooks para integrar a Kortilink na tua stack. Cria links, gere bio pages, recebe eventos em tempo real, e construa o que quiseres em cima.

Base URL: https://kortilink.com

Versão: v1 (estável)

💡 Não há SDK ainda — usa fetch, axios, requests, ou qualquer cliente HTTP. Os exemplos abaixo usam cURL e Node.js.

Autenticação

Todas as chamadas a /api/v1/* exigem um header Authorization com a tua API key:

Authorization: Bearer kl_a1b2c3d4e5f6...

Cria a tua key em /dashboard → cartão "🔌 API & Webhooks". A key começa por kl_ e só é mostrada uma vez. Guarda em local seguro (gestor de passwords, env var).

⚠️ Nunca exponhas a key no frontend ou em repositórios públicos. Trata-a como uma password.

👤 Obter perfil — GET /v1/me

GET/api/v1/me

Devolve dados do utilizador associado à API key.

Exemplo

curl https://kortilink.com/api/v1/me \
  -H "Authorization: Bearer kl_..."

Resposta 200

{
  "id": 42,
  "email": "creator@exemplo.com",
  "name": "Maria",
  "plan": "pro",
  "isPro": true,
  "createdAt": "2026-01-15T10:30:00Z",
  "referralCode": "MARIA8X2"
}

🌐 Obter Bio Page — GET /v1/bio

GET/api/v1/bio

Devolve metadados da bio page do utilizador.

{
  "bio": {
    "slug": "maria",
    "title": "Maria · Designer",
    "description": "UX em Lisboa 🇵🇹",
    "avatar": "https://...",
    "url": "https://kortilink.com/b/maria",
    "blocks_count": 8,
    "updatedAt": "2026-04-30T12:00:00Z"
  }
}

📊 Analytics da Bio — GET /v1/analytics/bio

GET/api/v1/analytics/bio?days=30

Retorna views, clicks, CTR, e breakdowns por país/device/bloco.

{
  "period_days": 30,
  "views": 1284,
  "clicks": 387,
  "ctr": 30.1,
  "by_country": { "PT": 720, "BR": 320, "ES": 180, "FR": 64 },
  "by_device":  { "mobile": 980, "desktop": 280, "tablet": 24 },
  "by_block":   { "abc12def": 145, "xyz78ghi": 98 }
}

🪝 Webhooks

Recebe eventos em tempo real. Cria um webhook em /dashboard com:

  • URL HTTPS que aceita POST
  • Lista de eventos a receber (ou * para todos)

Recebes um secret único por webhook. Cada delivery vem com X-Kortilink-Signature: sha256=<hmac> que assinas o body com esse secret.

Estrutura do payload

{
  "event": "link.created",
  "timestamp": "2026-04-30T15:00:00Z",
  "data": {
    "short_code": "bf26",
    "short_url": "https://kortilink.com/bf26",
    "original_url": "https://...",
    "title": "BF 2026"
  }
}

Headers

POST /your/webhook/endpoint HTTP/1.1
Content-Type: application/json
User-Agent: Kortilink-Webhooks/1.0
X-Kortilink-Event: link.created
X-Kortilink-Signature: sha256=a7b8c9...
X-Kortilink-Webhook-Id: 7

Eventos disponíveis

EventoQuando dispara
link.createdNovo link encurtado (UI ou API)
link.deletedLink apagado
link.clickedCada clique no link (high-volume)
bio.publishedBio guardada/publicada
bio.viewedVisita à bio page
bio.clickClick num bloco da bio
bio.email_captureInscrição via bloco Email
*Todos os eventos (catch-all)

🔐 Verificar assinatura

Recomendado em produção. Compara o X-Kortilink-Signature com um HMAC-SHA256 do body usando o secret do webhook.

Node.js

const crypto = require('crypto');

function verifyKortilinkWebhook(rawBody, signatureHeader, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signatureHeader),
    Buffer.from(expected)
  );
}

// Express:
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-kortilink-signature'];
  if (!verifyKortilinkWebhook(req.body, sig, process.env.KL_SECRET)) {
    return res.status(401).send('invalid signature');
  }
  const event = JSON.parse(req.body.toString());
  // ...processa
  res.sendStatus(200);
});

Python

import hmac, hashlib

def verify_kortilink(raw_body, signature, secret):
    expected = 'sha256=' + hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)
📨 Retry: falhas (4xx, 5xx, timeout 8s) são retentadas até 3× com 5/10s de espera. Após 20 falhas consecutivas, o webhook é desativado automaticamente.

Códigos de erro

CódigoSignificado
400Body inválido / parâmetros em falta
401API key inválida ou em falta
403Plano insuficiente (Pro requerido)
404Recurso não existe
409Conflito (ex: custom_code já tomado)
429Rate limit excedido
500Erro interno (inclui reqId para suporte)

Rate limits

EndpointLimite
POST /v1/links60 req/min
Outros /v1/*20-30 req/min
Gestão de keys/webhooks (UI)5 req/min

Excedido → 429 Too Many Requests. Espera 60s e tenta de novo.

Precisas de ajuda?

Bugs, dúvidas ou pedidos de novos endpoints — contacta suporte@kortilink.com ou abre uma issue.