Skip to main content
Webhook é o caminho recomendado para produção. O fluxo é simples:
1. Configure o webhook endpoint (uma vez, no dashboard)
2. POST /api/v1/bureau/runs → receba run_id
3. kycert processa o bureau (5 a 30 segundos)
4. kycert faz POST para seu endpoint com o resultado
5. Verifique a assinatura
6. Aja com base na decision

Passo 1: Configure o endpoint (uma vez)

No dashboard kycert, vá em Settings → Webhooks e cadastre o URL HTTPS do seu servidor. O kycert enviará todos os resultados para esse endpoint. Alternativamente, passe webhook_url no corpo de cada POST /runs para sobrescrever o padrão por requisição.

Passo 2: Criar o run

curl -X POST https://admin.kycert.com.br/api/v1/bureau/runs \
  -H "x-api-key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "template_id": "550e8400-e29b-41d4-a716-446655440000",
    "subject": {
      "type": "pf",
      "doc": "12345678901",
      "name": "João Silva"
    },
    "external_id": "cust_abc123"
  }'
Resposta:
{
  "run_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "queued",
  "estimated_seconds": 15,
  "livemode": true
}

Passo 3–4: kycert processa e entrega o resultado

O kycert consulta as fontes em paralelo e entrega o resultado no seu endpoint quando pronto. Você não precisa fazer nada neste passo.

Passo 5: Receber e verificar o webhook

Sempre verifique a assinatura. Qualquer pessoa na internet pode fazer POST para o seu endpoint — a verificação garante que o evento veio realmente do kycert.
import express from 'express'
import crypto from 'crypto'

function verifyKycertSignature(
  payload: Buffer,
  signature: string,
  secret: string,
): boolean {
  // Formato: "t=1234567890,v1=abc123..."
  const parts = Object.fromEntries(
    signature.split(',').map(p => p.split('=') as [string, string])
  )
  const timestamp = parts['t']
  const expected  = parts['v1']
  if (!timestamp || !expected) return false

  // Rejeitar eventos com mais de 5 minutos de diferença
  if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) return false

  const computed = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${payload.toString()}`)
    .digest('hex')

  return crypto.timingSafeEqual(
    Buffer.from(computed),
    Buffer.from(expected),
  )
}

const app = express()

app.post(
  '/webhooks/kycert',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.headers['kycert-signature'] as string
    const secret    = process.env.KYCERT_WEBHOOK_SECRET!

    if (!verifyKycertSignature(req.body, signature, secret)) {
      return res.sendStatus(401)
    }

    // Responda 200 imediatamente — processe de forma assíncrona
    res.sendStatus(200)

    const event = JSON.parse(req.body.toString())
    setImmediate(() => handleKycertEvent(event))
  }
)

async function handleKycertEvent(event: Record<string, unknown>) {
  if (event['event'] === 'run.completed') {
    const data = event['data'] as Record<string, unknown>
    const { decision, risk_band, external_id } = data as {
      decision: string
      risk_band: string
      external_id: string
    }
    console.log(`Run ${external_id}: ${decision} (${risk_band})`)
    // ex: atualizar status do cliente no banco de dados
  }
}

Passo 6: Agir com base na decision

decisionrisk_band típicoO que fazer
approvedbaixo / medioProsseguir com onboarding ou operação
rejectedaltoRecusar — não reverter sem análise manual do compliance
reviewmedio / altoAguardar decisão do analista no dashboard kycert

Payload completo do webhook

{
  "id": "evt_01J4...",
  "object": "event",
  "event": "run.completed",
  "created": 1718200818,
  "livemode": true,
  "data": {
    "object": "run",
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "completed",
    "decision": "approved",
    "operative_decision": null,
    "risk_band": "baixo",
    "subject_type": "pf",
    "template_id": "661e9511-f3ac-52e5-b827-557766551111",
    "external_id": "cust_abc123",
    "metadata": { "channel": "app_mobile" },
    "created_at": "2026-06-12T14:00:00Z",
    "completed_at": "2026-06-12T14:00:18Z"
  }
}

Boas práticas

  • Responda 200 imediatamente e processe de forma assíncrona — seu endpoint tem 30 segundos antes do kycert considerar falha
  • Seja idempotente — o mesmo evento pode ser entregue mais de uma vez em caso de retry
  • Registre o id do evento para deduplicação
  • Nunca retorne 4xx em erros de lógica — use 2xx e trate o erro internamente (4xx faz o kycert parar de tentar)
Consulte a política completa de retry em Webhooks → Overview.