Webhooks
Um webhook avisa o seu sistema de que algo mudou em um repositório, para que ele não precise consultar o servidor. O worker do Arkvory envia um POST HTTP para a sua URL a cada evento do feed de alterações do repositório. Use-o para iniciar uma implantação quando um build é publicado ou para atualizar um cache quando um caminho recebe uma nova versão.
O administrador define as assinaturas em um arquivo. Ainda não há API nem página do console para elas. Você também pode ler o feed por conta própria com GET /api/v1/repositories/<repository>/changes.
Como a entrega funciona
- Uma assinatura acompanha um repositório e envia para uma URL.
- Os eventos chegam em ordem, um de cada vez. O próximo espera até o receptor responder com um status
2xx. - A entrega ocorre pelo menos uma vez. Após uma falha ou uma resposta perdida, o mesmo evento pode chegar de novo. Cada evento tem um
idestável: desduplique por ele. - Uma assinatura nova recebe apenas eventos novos. Eventos anteriores à sua criação não são enviados.
- Um receptor fora do ar atrasa só a sua própria assinatura. O evento espera. O Arkvory tenta de novo após 12 segundos, dobra a pausa até uma hora e envia os eventos em ordem quando o receptor voltar a responder. Uploads e downloads nunca esperam por um webhook.
Configurar uma assinatura
Você precisa de acesso aos arquivos do servidor e de permissão para reiniciar o worker. Veja Configuração.
- Crie um arquivo com o segredo de assinatura: pelo menos 16 caracteres imprimíveis aleatórios, por exemplo
/root/ci.secret. O receptor precisa do mesmo segredo. - Execute
configurecom a assinatura. O comando valida o que você informou, copia o segredo paraconfig/webhooks, escreve o arquivo de assinaturas e a configuração, reinicia os serviços e restaura a configuração anterior se eles não ficarem prontos. Ele não contata o receptor.
sudo arkvory configure --root /opt/proanima-arkvory \
--webhook ci --webhook-repository releases \
--webhook-url https://ci.example.com/hooks/arkvory \
--webhook-secret-file /root/ci.secret \
--webhook-actions artifact.publish- Procure
webhook.startedno log do worker, publique um arquivo e observe o seu receptor.
O comando escreve config/webhooks/webhooks.json, que você também pode escrever à mão e indicar em ARKVORY_WEBHOOKS_FILE. Para alterar uma assinatura, execute o comando de novo com o mesmo nome. --webhook-detach ci a remove: os arquivos dela são apagados e a posição é esquecida. Uma chamada altera uma assinatura e não pode ser combinada com outras opções de configure.
O arquivo tem estes campos:
{
"webhooks": [
{
"id": "ci",
"repository": "releases",
"url": "https://ci.example.com/hooks/arkvory",
"secretFile": "/opt/proanima-arkvory/config/webhooks/ci.secret",
"actions": ["artifact.publish"]
}
]
}| Campo | Significado |
|---|---|
id | Nome da assinatura: de 1 a 64 caracteres a-z, 0-9, _ e -. O progresso dela é guardado com esse nome. |
repository | O repositório cujo feed é enviado. |
url | O receptor. HTTPS, sem nome de usuário, query nem fragmento, até 2048 caracteres. HTTP simples só é permitido para localhost, 127.0.0.1 e [::1]. |
secretFile | Caminho absoluto do arquivo com o segredo de assinatura. |
nextSecretFile | Segundo segredo opcional para uma rotação. |
actions | Lista opcional de ações do feed a enviar, por exemplo artifact.publish, artifact.delete, asset.replace, stage.added e package.register. Sem ela, todas as ações são enviadas. |
Um arquivo incorreto interrompe o worker na inicialização com worker.unavailable. São permitidas até 16 assinaturas. Em uma instalação com Compose, o comando também monta config/webhooks no contêiner do worker.
A requisição
| Cabeçalho | Valor |
|---|---|
Content-Type | application/json |
X-Arkvory-Delivery | O id do evento. É o mesmo em cada repetição de um evento. |
X-Arkvory-Event | A ação do feed, por exemplo artifact.publish. |
X-Arkvory-Timestamp | Hora Unix em segundos em que a requisição foi assinada. |
X-Arkvory-Signature | sha256=<hex>. Durante uma rotação há dois valores separados por vírgula. |
O corpo é JSON:
{
"id": "releases:128",
"repository": "releases",
"sequence": "128",
"action": "artifact.publish",
"artifactId": "00000000-0000-4000-8000-000000000001",
"detail": null
}sequence é a posição no feed, uma string decimal. detail é o caminho do arquivo ou o estágio nas ações que o têm. O corpo não tem autor, conteúdo de arquivo nem metadados: use artifactId para ler o estado atual pela API.
Responda com qualquer status 2xx em até 10 segundos. Um status 3xx (redirecionamentos não são seguidos), 4xx, 5xx, um erro de conexão ou um tempo esgotado conta como falha. O Arkvory lê no máximo 4 KiB da resposta e a ignora.
Verificar a assinatura
A assinatura é HMAC-SHA256 com o seu segredo sobre o texto <timestamp>.<body>, escrita como sha256= e o resumo em hexadecimal. Verifique-a antes de confiar em uma requisição:
- Leia o corpo bruto, antes de qualquer análise de JSON.
- Recuse a requisição se o carimbo de tempo diferir do seu relógio em mais de 5 minutos.
- Calcule a assinatura e compare com uma função de tempo constante. Se o cabeçalho tiver dois valores, aceite qualquer um.
Node.js:
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(secret, headers, rawBody) {
const timestamp = headers['x-arkvory-timestamp'];
if (!/^\d{1,12}$/.test(timestamp ?? '')) return false;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected = Buffer.from(
'sha256=' + createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex'),
);
return String(headers['x-arkvory-signature'] ?? '')
.split(',')
.some((given) => {
const actual = Buffer.from(given.trim());
return actual.length === expected.length && timingSafeEqual(actual, expected);
});
}Python:
import hashlib, hmac, time
def verify(secret: bytes, headers, raw_body: bytes) -> bool:
timestamp = headers.get("X-Arkvory-Timestamp", "")
if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300:
return False
digest = hmac.new(secret, timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
expected = "sha256=" + digest
given = headers.get("X-Arkvory-Signature", "").split(",")
return any(hmac.compare_digest(part.strip(), expected) for part in given)Tratar entregas repetidas
- Guarde o
idde cada evento tratado e ignore uma repetição. - Responda rápido: coloque o trabalho em uma fila e devolva
204. Um receptor lento atrasa todos os eventos seguintes da sua assinatura. - Uma repetição não significa que a operação ocorreu duas vezes. Trate o evento como uma dica e leia o estado atual pela API.
Rotacionar o segredo
- Execute o mesmo comando com
--webhook-secret-filepara o segredo atual e--webhook-next-secret-filepara o novo. Cada requisição passa a levar duas assinaturas. - Troque o receptor para o novo segredo. Um receptor que aceita qualquer uma das assinaturas continua funcionando nesse meio-tempo.
- Execute o comando de novo com o arquivo do novo segredo em
--webhook-secret-filee sem--webhook-next-secret-file.
Receptores privados e certificados
- O Arkvory recusa receptores em endereços loopback, privados, link-local e de metadados de nuvem, e nomes que resolvem para eles. Assim o servidor não pode ser usado para alcançar serviços internos.
- Para enviar a um receptor da sua rede, adicione a rede com
--webhook-allow-private, por exemplo--webhook-allow-private 10.20.0.0/16(ARKVORY_WEBHOOKS_ALLOW_PRIVATE). Ela permanece até a última assinatura ser removida. - Se o certificado do receptor vem da sua própria autoridade, adicione um arquivo PEM dessa autoridade com
--webhook-ca-file(ARKVORY_WEBHOOKS_CA_FILE). O certificado é sempre verificado.
Monitorar webhooks
O worker escreve webhook.step_failed com um errorCode quando uma entrega falha e webhook.recovered quando volta a funcionar. As métricas arkvory_webhook_failing e arkvory_webhook_last_success_timestamp_seconds e o alerta ArkvoryWebhookFailing estão descritos em Monitoramento. webhook.stopped marca o fim do ciclo de uma assinatura: ao parar ou quando o worker perde a posse do armazenamento. webhook.prune_failed significa que o worker não conseguiu esquecer as assinaturas removidas ao iniciar; ele tenta de novo no próximo início. Os códigos unavailable e internal aparecem só no log, não em arkvory_webhook_failing.
Uma assinatura que não avança
O Arkvory nunca pula um evento: é isso que torna a entrega confiável. Por isso, um receptor que recusa repetidamente um evento, por exemplo com 400 porque não consegue interpretá-lo, para toda a sua assinatura. Nenhum evento posterior dessa assinatura é enviado; as outras assinaturas continuam funcionando. O worker escreve webhook.step_failed com http_4xx e tenta de novo com pausas de até uma hora, arkvory_webhook_failing fica em 1 e o alerta ArkvoryWebhookFailing dispara após 15 minutos.
- Corrija o receptor para que ele responda ao evento com
2xx. O Arkvory o envia na próxima tentativa, no máximo cerca de uma hora depois, e em seguida todos os eventos em espera, em ordem. Executarconfigurede novo com o mesmo nome e repositório mantém a posição. - Se o evento nunca puder ser aceito, remova a assinatura com
--webhook-detache adicione-a de novo. Ela começa no fim do feed: os eventos em espera não são enviados. Leia-os no feed de mudanças depois do últimosequenceque seu receptor processou.
Solução de problemas
errorCode | Causa e o que fazer |
|---|---|
blocked | O endereço do receptor não é permitido (loopback, privado, link-local ou de metadados). Use um endereço público ou adicione a rede a ARKVORY_WEBHOOKS_ALLOW_PRIVATE. |
timeout | Sem resposta em 10 segundos. Responda mais rápido e coloque o trabalho em fila. |
network | Conexão recusada, nome não encontrado ou conexão redefinida. Verifique a URL, o DNS e o firewall a partir do servidor. |
tls | O certificado não é confiável, expirou ou tem o nome errado. Corrija-o ou defina ARKVORY_WEBHOOKS_CA_FILE. |
redirect | O receptor respondeu 3xx. Redirecionamentos não são seguidos: use a URL final. |
http_4xx | O receptor recusou a requisição. Verifique a checagem de assinatura, o caminho e a chave dele. |
http_5xx | O receptor falhou. O Arkvory continua tentando, com até uma hora entre as tentativas. |
secret | O arquivo de segredo não existe, não pode ser lido ou tem menos de 16 bytes. |
unavailable | O worker não conseguiu ler o feed ou o estado da assinatura no banco de dados. Nada foi enviado; ele tenta de novo após 5 segundos. Verifique o banco de dados. |
internal | Um erro inesperado no worker. Nada foi enviado; ele tenta de novo após 5 segundos. Relate-o junto com o log do worker. |
Nada chega? Verifique se webhook.started está no log do worker, se ARKVORY_WEBHOOKS_FILE está definido, o nome do repositório e o filtro actions. Uma assinatura nova envia apenas eventos posteriores ao seu primeiro passo.