Receber webhooks do GitHub
A assinatura HMAC sobre os bytes originais, a entrega que chega duas vezes, e o 401 que não processa nada.
Um webhook é um POST que chega de fora dizendo “houve um push”. Qualquer um que descubra a URL pode mandar o mesmo POST — por isso o GitHub assina o corpo com HMAC-SHA256, e o cabeçalho X-Hub-Signature-256 traz a assinatura. Conferi-la é a única coisa que separa um evento do GitHub de um pedido forjado.
No Kiln#
dataforge
adopt Kiln
adopt Arcane.GitHub as GH
steady SEGREDO := "troque-por-OS.env"
vistos := []
processados := []
server hooks on 0:
route POST "/github":
monitor:
// raw_body: os BYTES que chegaram. Decodificar e re-serializar o
// JSON muda espacos e ordem das chaves, e a assinatura nao bate.
e := GH.evento_de_webhook(headers, req["raw_body"], SEGREDO)
handle Error:
respond 401 json {"erro": "assinatura invalida"}
// O GitHub reentrega. Processar duas vezes e o proximo defeito.
given e["entrega"] in vistos:
respond 200 json {"repetida": yes}
vistos.append(e["entrega"])
given e["tipo"] is "pull_request" and e["acao"] is "opened":
processados.append(e["carga"]["number"])
respond 202 json {"ok": yes}
corpo := to_json({"action": "opened", "number": 7})
cab := {"X-GitHub-Event": "pull_request", "X-GitHub-Delivery": "d-1",
"X-Hub-Signature-256": GH.assinatura(SEGREDO, corpo)}
assert Kiln.test(hooks, "POST", "/github", corpo, cab)["status"] is 202
assert Kiln.test(hooks, "POST", "/github", corpo, cab)["body"]["repetida"]
assert processados is [7]
forjado := cab with {}
forjado["X-Hub-Signature-256"] := GH.assinatura("outro", corpo)
assert Kiln.test(hooks, "POST", "/github", corpo, forjado)["status"] is 401
out "webhook: assinado, idempotente, e o forjado recusado"As quatro decisões#
| Decisão | Sem ela |
|---|---|
conferir sobre raw_body | a assinatura nunca bate, e alguém desliga a conferência para “funcionar” |
| comparar em tempo constante | == vaza, pelo tempo, quantos caracteres da assinatura acertaram |
guardar o X-GitHub-Delivery | a reentrega cria o mesmo deploy, o mesmo e-mail, a mesma cobrança duas vezes |
| responder rápido (202) e processar depois | o GitHub desiste em 10 s e marca a entrega como falha |
Continue em A API do GitHub.