Webhooks
O Corban envia um POST para a URL cadastrada pelo seu canal a cada mudança de status de uma
proposta dos seus links.
Cadastrar
Seção intitulada “Cadastrar”curl -s -X PUT https://sandbox-api.eagleix-corban.com.br/v1/webhooks \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{"url":"https://api.suaempresa.com.br/webhooks/corban"}'$body = '{"url":"https://api.suaempresa.com.br/webhooks/corban"}'$body | curl.exe -s -X PUT "https://sandbox-api.eagleix-corban.com.br/v1/webhooks" -H "Authorization: Bearer $env:ACCESS_TOKEN" -H "Content-Type: application/json" --data-binary "@-"$h = @{ Authorization = "Bearer $($env:ACCESS_TOKEN)" }$body = '{"url":"https://api.suaempresa.com.br/webhooks/corban"}'Invoke-RestMethod -Method Put -Uri "https://sandbox-api.eagleix-corban.com.br/v1/webhooks" ` -Headers $h ` -ContentType "application/json" -Body $bodyconst res = await fetch('https://sandbox-api.eagleix-corban.com.br/v1/webhooks', { method: 'PUT', headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "url": "https://api.suaempresa.com.br/webhooks/corban" }),})const data = await res.json()import os, requests
r = requests.request( "PUT", "https://sandbox-api.eagleix-corban.com.br/v1/webhooks", headers={"Authorization": f"Bearer {os.environ['ACCESS_TOKEN']}"}, json={ "url": "https://api.suaempresa.com.br/webhooks/corban" }, timeout=15,)print(r.status_code, r.json())Em Import, escolha Raw text e cole o comando. A variável {{ACCESS_TOKEN}} é criada na coleção.
curl --location --request PUT 'https://sandbox-api.eagleix-corban.com.br/v1/webhooks' \ --header 'Authorization: Bearer {{ACCESS_TOKEN}}' \ --header 'Content-Type: application/json' \ --data '{"url":"https://api.suaempresa.com.br/webhooks/corban"}'A resposta traz o segredo (whsec_...), exibido uma única vez. Guarde-o.
- Um webhook por canal. No momento, apenas o sandbox está disponível.
- Filtrar eventos: envie
"eventos": ["proposta.aprovada", "proposta.recusada"]. Sem a lista, todos os eventos são enviados. - Requisitos da URL:
https, domínio público, sem endereço IP, sem credenciais na URL e fora de redes internas. - Testar:
POST /v1/webhooks/testeenvia um eventowebhook.testeassinado e devolve o resultado da entrega.
Eventos
Seção intitulada “Eventos”| Evento | Quando |
|---|---|
proposta.criada |
A proposta foi criada no banco. |
proposta.aguardando_formalizacao |
O link de assinatura foi gerado para o cliente. |
proposta.formalizada |
O cliente assinou. |
proposta.aprovada |
O banco aprovou. |
proposta.recusada |
O banco recusou (final). |
proposta.cancelada |
A proposta foi cancelada (final). |
proposta.falha |
O banco recusou a criação; o cliente pode tentar de novo no mesmo link. |
{ "id": "5d1f7c52-1d1c-4c0b-9a55-2f7a0b6c9e11", "tipo": "proposta.aprovada", "ocorridoEm": "2026-10-13T18:20:11Z", "ambiente": "SANDBOX", "link": { "id": "fd6e64ed-5ebe-434d-9351-983ff129e9c0", "referenciaExterna": "promotor-42" }, "proposta": { "numero": 100004, "status": "APROVADA", "valor": 20000, "parcelas": 36, "valorParcela": 971.54 }}Os eventos não levam dados pessoais. Relacione-os aos seus registros por link.id e
link.referenciaExterna.
Headers
Seção intitulada “Headers”| Header | Conteúdo |
|---|---|
X-Corban-Signature |
t=<unix>,v1=<hex> (durante a troca de segredo, dois v1) |
X-Corban-Event-Id |
Igual ao id do corpo; o mesmo em todas as tentativas |
X-Corban-Event-Type |
Igual ao tipo |
X-Corban-Delivery-Attempt |
Número da tentativa (1 a 7) |
Validar a assinatura
Seção intitulada “Validar a assinatura”v1 é o HMAC-SHA256, em hexadecimal, da string "<t>.<corpo bruto>" com o seu segredo.
import crypto from 'node:crypto'
export function verify(header, rawBody, secret, toleranceSec = 300) { const parts = header.split(',').map((p) => p.split('=')) const t = Number(parts.find(([k]) => k === 't')?.[1]) const sigs = parts.filter(([k]) => k === 'v1').map(([, v]) => v) if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex') return sigs.some( (s) => s.length === expected.length && crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected)), )}import hashlib, hmac, time
def verify(header: str, raw_body: bytes, secret: str, tolerance: int = 300) -> bool: parts = [p.split("=", 1) for p in header.split(",")] t = next((int(v) for k, v in parts if k == "t"), 0) sigs = [v for k, v in parts if k == "v1"] if not t or abs(time.time() - t) > tolerance: return False expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest() return any(hmac.compare_digest(s, expected) for s in sigs)func Verify(header string, rawBody []byte, secret string, tolerance time.Duration) bool { var t int64 var sigs []string for _, part := range strings.Split(header, ",") { k, v, _ := strings.Cut(part, "=") switch k { case "t": t, _ = strconv.ParseInt(v, 10, 64) case "v1": sigs = append(sigs, v) } } if t == 0 || time.Since(time.Unix(t, 0)).Abs() > tolerance { return false } mac := hmac.New(sha256.New, []byte(secret)) fmt.Fprintf(mac, "%d.", t) mac.Write(rawBody) expected := hex.EncodeToString(mac.Sum(nil)) for _, s := range sigs { if hmac.Equal([]byte(s), []byte(expected)) { return true } } return false}Recuse (400 ou 401) o evento cuja assinatura não for válida. Recuse também eventos com t
diferente do seu relógio em mais de 5 minutos: isso impede a reutilização de um envio antigo capturado
por terceiros.
Responder e lidar com repetição
Seção intitulada “Responder e lidar com repetição”-
Responda dentro do prazo. Qualquer
2xxem até 10 segundos confirma o recebimento. Recomendamos gravar o evento, responder200e processá-lo depois, em uma fila do seu sistema. Redirecionamentos não são seguidos. -
Descarte duplicados. A entrega é pelo menos uma vez: o mesmo evento pode ser recebido mais de uma vez. Use o
id(ou o headerX-Corban-Event-Id) como chave de deduplicação. -
Ordene pelo horário. Os eventos podem chegar fora de ordem. Use
ocorridoEmpara ordená-los e considere que o status da proposta nunca retrocede.
Retentativas
Seção intitulada “Retentativas”Se o seu endpoint falhar ou não responder no prazo, o Corban tenta novamente após 1 min, 5 min,
30 min, 2 h, 6 h e 24 h (7 tentativas no total) e, em seguida, marca a entrega como FALHOU.
- Histórico:
GET /v1/webhooks/entregas(filtre porstatus=FALHOU). - Reenvio manual:
POST /v1/webhooks/entregas/{id}/reenviar.
Gerenciar o webhook
Seção intitulada “Gerenciar o webhook”Os exemplos usam os mesmos caminhos da referência da API.
Consultar o webhook cadastrado (o segredo não é devolvido):
curl -s -X GET https://sandbox-api.eagleix-corban.com.br/v1/webhooks \ -H "Authorization: Bearer $ACCESS_TOKEN"curl.exe -s -X GET "https://sandbox-api.eagleix-corban.com.br/v1/webhooks" -H "Authorization: Bearer $env:ACCESS_TOKEN"$h = @{ Authorization = "Bearer $($env:ACCESS_TOKEN)" }Invoke-RestMethod -Method Get -Uri "https://sandbox-api.eagleix-corban.com.br/v1/webhooks" ` -Headers $hconst res = await fetch('https://sandbox-api.eagleix-corban.com.br/v1/webhooks', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}`, },})const data = await res.json()import os, requests
r = requests.request( "GET", "https://sandbox-api.eagleix-corban.com.br/v1/webhooks", headers={"Authorization": f"Bearer {os.environ['ACCESS_TOKEN']}"}, timeout=15,)print(r.status_code, r.json())Em Import, escolha Raw text e cole o comando. A variável {{ACCESS_TOKEN}} é criada na coleção.
curl --location --request GET 'https://sandbox-api.eagleix-corban.com.br/v1/webhooks' \ --header 'Authorization: Bearer {{ACCESS_TOKEN}}'Enviar um evento de teste (webhook.teste, assinado) para a URL cadastrada:
curl -s -X POST https://sandbox-api.eagleix-corban.com.br/v1/webhooks/teste \ -H "Authorization: Bearer $ACCESS_TOKEN"curl.exe -s -X POST "https://sandbox-api.eagleix-corban.com.br/v1/webhooks/teste" -H "Authorization: Bearer $env:ACCESS_TOKEN"$h = @{ Authorization = "Bearer $($env:ACCESS_TOKEN)" }Invoke-RestMethod -Method Post -Uri "https://sandbox-api.eagleix-corban.com.br/v1/webhooks/teste" ` -Headers $hconst res = await fetch('https://sandbox-api.eagleix-corban.com.br/v1/webhooks/teste', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}`, },})const data = await res.json()import os, requests
r = requests.request( "POST", "https://sandbox-api.eagleix-corban.com.br/v1/webhooks/teste", headers={"Authorization": f"Bearer {os.environ['ACCESS_TOKEN']}"}, timeout=15,)print(r.status_code, r.json())Em Import, escolha Raw text e cole o comando. A variável {{ACCESS_TOKEN}} é criada na coleção.
curl --location --request POST 'https://sandbox-api.eagleix-corban.com.br/v1/webhooks/teste' \ --header 'Authorization: Bearer {{ACCESS_TOKEN}}'Listar as entregas (use status=FALHOU para ver apenas as que falharam):
curl -s -X GET https://sandbox-api.eagleix-corban.com.br/v1/webhooks/entregas?status=FALHOU \ -H "Authorization: Bearer $ACCESS_TOKEN"curl.exe -s -X GET "https://sandbox-api.eagleix-corban.com.br/v1/webhooks/entregas?status=FALHOU" -H "Authorization: Bearer $env:ACCESS_TOKEN"$h = @{ Authorization = "Bearer $($env:ACCESS_TOKEN)" }Invoke-RestMethod -Method Get -Uri "https://sandbox-api.eagleix-corban.com.br/v1/webhooks/entregas?status=FALHOU" ` -Headers $hconst res = await fetch('https://sandbox-api.eagleix-corban.com.br/v1/webhooks/entregas?status=FALHOU', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}`, },})const data = await res.json()import os, requests
r = requests.request( "GET", "https://sandbox-api.eagleix-corban.com.br/v1/webhooks/entregas?status=FALHOU", headers={"Authorization": f"Bearer {os.environ['ACCESS_TOKEN']}"}, timeout=15,)print(r.status_code, r.json())Em Import, escolha Raw text e cole o comando. A variável {{ACCESS_TOKEN}} é criada na coleção.
curl --location --request GET 'https://sandbox-api.eagleix-corban.com.br/v1/webhooks/entregas?status=FALHOU' \ --header 'Authorization: Bearer {{ACCESS_TOKEN}}'Reenviar uma entrega, usando o id da entrega retornado na listagem:
curl -s -X POST https://sandbox-api.eagleix-corban.com.br/v1/webhooks/entregas/$ENTREGA_ID/reenviar \ -H "Authorization: Bearer $ACCESS_TOKEN"curl.exe -s -X POST "https://sandbox-api.eagleix-corban.com.br/v1/webhooks/entregas/$($env:ENTREGA_ID)/reenviar" -H "Authorization: Bearer $env:ACCESS_TOKEN"$h = @{ Authorization = "Bearer $($env:ACCESS_TOKEN)" }Invoke-RestMethod -Method Post -Uri "https://sandbox-api.eagleix-corban.com.br/v1/webhooks/entregas/$($env:ENTREGA_ID)/reenviar" ` -Headers $hconst res = await fetch(`https://sandbox-api.eagleix-corban.com.br/v1/webhooks/entregas/${process.env.ENTREGA_ID}/reenviar`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}`, },})const data = await res.json()import os, requests
r = requests.request( "POST", f"https://sandbox-api.eagleix-corban.com.br/v1/webhooks/entregas/{os.environ['ENTREGA_ID']}/reenviar", headers={"Authorization": f"Bearer {os.environ['ACCESS_TOKEN']}"}, timeout=15,)print(r.status_code, r.json())Em Import, escolha Raw text e cole o comando. A variável {{ACCESS_TOKEN}} é criada na coleção.
curl --location --request POST 'https://sandbox-api.eagleix-corban.com.br/v1/webhooks/entregas/{{ENTREGA_ID}}/reenviar' \ --header 'Authorization: Bearer {{ACCESS_TOKEN}}'Remover o webhook:
curl -s -X DELETE https://sandbox-api.eagleix-corban.com.br/v1/webhooks \ -H "Authorization: Bearer $ACCESS_TOKEN"curl.exe -s -X DELETE "https://sandbox-api.eagleix-corban.com.br/v1/webhooks" -H "Authorization: Bearer $env:ACCESS_TOKEN"$h = @{ Authorization = "Bearer $($env:ACCESS_TOKEN)" }Invoke-RestMethod -Method Delete -Uri "https://sandbox-api.eagleix-corban.com.br/v1/webhooks" ` -Headers $hconst res = await fetch('https://sandbox-api.eagleix-corban.com.br/v1/webhooks', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}`, },})const data = await res.json()import os, requests
r = requests.request( "DELETE", "https://sandbox-api.eagleix-corban.com.br/v1/webhooks", headers={"Authorization": f"Bearer {os.environ['ACCESS_TOKEN']}"}, timeout=15,)print(r.status_code, r.json())Em Import, escolha Raw text e cole o comando. A variável {{ACCESS_TOKEN}} é criada na coleção.
curl --location --request DELETE 'https://sandbox-api.eagleix-corban.com.br/v1/webhooks' \ --header 'Authorization: Bearer {{ACCESS_TOKEN}}'Trocar o segredo sem perder eventos
Seção intitulada “Trocar o segredo sem perder eventos”POST /v1/webhooks/segredo gera um novo segredo, exibido uma única vez:
curl -s -X POST https://sandbox-api.eagleix-corban.com.br/v1/webhooks/segredo \ -H "Authorization: Bearer $ACCESS_TOKEN"curl.exe -s -X POST "https://sandbox-api.eagleix-corban.com.br/v1/webhooks/segredo" -H "Authorization: Bearer $env:ACCESS_TOKEN"$h = @{ Authorization = "Bearer $($env:ACCESS_TOKEN)" }Invoke-RestMethod -Method Post -Uri "https://sandbox-api.eagleix-corban.com.br/v1/webhooks/segredo" ` -Headers $hconst res = await fetch('https://sandbox-api.eagleix-corban.com.br/v1/webhooks/segredo', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}`, },})const data = await res.json()import os, requests
r = requests.request( "POST", "https://sandbox-api.eagleix-corban.com.br/v1/webhooks/segredo", headers={"Authorization": f"Bearer {os.environ['ACCESS_TOKEN']}"}, timeout=15,)print(r.status_code, r.json())Em Import, escolha Raw text e cole o comando. A variável {{ACCESS_TOKEN}} é criada na coleção.
curl --location --request POST 'https://sandbox-api.eagleix-corban.com.br/v1/webhooks/segredo' \ --header 'Authorization: Bearer {{ACCESS_TOKEN}}'O segredo anterior continua válido
por 24 horas e, nesse período, cada envio traz as duas assinaturas v1. Atualize o segredo no seu
sistema dentro desse prazo e nenhum evento será rejeitado na troca.
