Webhooks
fremforge levererar webhooks som i samma leverans är kompatibla med både Forgejos eget signaturschema och det GitHub-kompatibla schemat. Mottagare som är byggda för något av dem fungerar utan ändringar. Välj den header du litar på och verifiera mot din delade secret.
Den här sidan beskriver kontraktet: vad fremforge skickar, hur du verifierar det och vilka garantier du får kring omförsök och TLS.
Var du konfigurerar vad: webhooks på organisationsnivå (vilka händelser de prenumererar på, mål-URL, signeringssecret) skapar du i fremforges administration på /<org>/_admin/webhooks, där du också hittar leveranshistorik och omsändning. Forgejos sida för hooks på organisationsnivå, /org/<org>/settings/hooks, skickar dig vidare dit. Forgejo-hooks på repo-nivå (avgränsade till ett repo) ställer du in i Forgejos gränssnitt på /<org>/<repo>/settings/hooks.
Snabbreferens
POST <your-webhook-url> HTTP/1.1
Host: receiver.example.com
User-Agent: fremforge-webhook/1
Content-Type: application/json
X-Forgejo-Event: push
X-Gitea-Delivery: 7c4e2ecd-79b5-4e1d-8eaa-0d1c2c8e5fbd
X-Forgejo-Signature: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
X-Hub-Signature-256: sha256=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
X-Hub-Signature: sha1=2fd4e1c67a2d28fced849ee1bb76e7391b93eb12
{<your event payload as JSON>}| Header | Betydelse |
|---|---|
X-Forgejo-Event | Händelsetyp (push, pull_request, issues, release osv.) |
X-Gitea-Delivery | Unikt UUID per leveransförsök, används som skydd mot uppspelning |
X-Forgejo-Signature | HMAC-SHA256 av den råa kroppen, hexkodad, Forgejos eget format |
X-Hub-Signature-256 | HMAC-SHA256 av den råa kroppen, hexkodad med prefixet sha256=, GitHub-kompatibel |
X-Hub-Signature | HMAC-SHA1 av den råa kroppen, äldre GitHub-kompatibilitet, förlita dig inte på den |
Signeringsnyckeln är den secret du anger på webhooken i organisationens administrationsgränssnitt. Tom secret betyder att ingen signatur skickas; headrarna X-*-Signature saknas då. fremforges standardpolicy för plattformen nekar webhookkonfiguration utan secret i nya organisationer (Phase 1.5+).
Varför två signaturheadrar
Mottagare som är byggda för GitHub kontrollerar i regel X-Hub-Signature-256. Mottagare som är byggda direkt för Forgejo / Gitea kontrollerar X-Forgejo-Signature. Eftersom båda skickas i samma leverans behöver du inte röra mottagarkoden när du migrerar från GitHub, och nya integrationer kan välja det format som känns mest naturligt.
De två headrarna innehåller exakt samma HMAC-SHA256-värde, beräknat över samma råa kropp. Verifiera vilken som helst av dem; om båda går igenom är anropet äkta.
Verifiera en webhook: recept
Alla recept nedan verifierar den GitHub-kompatibla headern X-Hub-Signature-256. För att använda X-Forgejo-Signature i stället tar du bort steget som skalar av prefixet sha256=.
Node.js
import crypto from 'node:crypto';
function verifyFremforgeWebhook(rawBody, signatureHeader, secret) {
if (!signatureHeader || !signatureHeader.startsWith('sha256=')) return false;
const provided = Buffer.from(signatureHeader.slice('sha256='.length), 'hex');
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest();
return provided.length === expected.length &&
crypto.timingSafeEqual(provided, expected);
}
// In an Express / Hono / Fastify route:
// const isAuthentic = verifyFremforgeWebhook(
// req.rawBody, // Buffer or string of the EXACT bytes received
// req.headers['x-hub-signature-256'],
// process.env.FREMFORGE_WEBHOOK_SECRET,
// );
rawBody MÅSTE vara exakt de byte som togs emot, före all JSON-tolkning. Middleware som serialiserar om innehållet ger en annan hash och ett felaktigt underkännande.
Python
import hmac
import hashlib
def verify_fremforge_webhook(raw_body: bytes, signature_header: str, secret: str) -> bool:
if not signature_header or not signature_header.startswith("sha256="):
return False
provided = signature_header[len("sha256="):]
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(provided, expected)
# In a Flask / FastAPI handler:
# raw = request.get_data() # Flask: raw bytes; do NOT parse JSON first
# sig = request.headers.get("X-Hub-Signature-256", "")
# if not verify_fremforge_webhook(raw, sig, os.environ["FREMFORGE_WEBHOOK_SECRET"]):
# abort(401)Go
package webhook
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strings"
)
func VerifyFremforge(rawBody []byte, signatureHeader, secret string) bool {
if !strings.HasPrefix(signatureHeader, "sha256=") {
return false
}
provided, err := hex.DecodeString(strings.TrimPrefix(signatureHeader, "sha256="))
if err != nil {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(rawBody)
expected := mac.Sum(nil)
return hmac.Equal(provided, expected)
}Ruby
require 'openssl'
def verify_fremforge_webhook(raw_body, signature_header, secret)
return false unless signature_header&.start_with?('sha256=')
expected = OpenSSL::HMAC.hexdigest('sha256', secret, raw_body)
Rack::Utils.secure_compare(signature_header.sub('sha256=', ''), expected)
endRust
use hmac::{Hmac, Mac};
use sha2::Sha256;
use subtle::ConstantTimeEq;
type HmacSha256 = Hmac<Sha256>;
pub fn verify_fremforge_webhook(
raw_body: &[u8],
signature_header: &str,
secret: &[u8],
) -> bool {
let Some(hex_sig) = signature_header.strip_prefix("sha256=") else {
return false;
};
let Ok(provided) = hex::decode(hex_sig) else {
return false;
};
let mut mac = HmacSha256::new_from_slice(secret).expect("HMAC key");
mac.update(raw_body);
let expected = mac.finalize().into_bytes();
expected.as_slice().ct_eq(&provided).into()
}Skydd mot uppspelning
HMAC-signaturen bevisar äkthet (innehållet kom från fremforge, som känner till din delade secret) men inte att det är färskt. En angripare som fångar upp en giltig signerad webhook kan spela upp den igen hur många gånger som helst tills secret roteras.
Använd UUID:t i X-Gitea-Delivery som skydd mot uppspelning:
- Håll en liten cache (Redis, minne, till och med SQLite) med nyligen mottagna leverans-id:n.
- Avvisa varje inkommande webhook vars leverans-id redan finns i cachen.
- Sätt cachens TTL till 24 timmar. Alla fremforges omförsök sker inom ungefär 7,5 timmar, så det fönstret täcker dem; äldre uppspelningar är inte genomförbara med tanke på hur ofta secret roteras.
Hoppa över skyddet mot uppspelning bara om din hanterare är helt idempotent (samma leverans som tillämpas två gånger ger samma sluttillstånd). Idempotenta hanterare är den renaste lösningen; en uppspelningscache är reservlösningen när hanteraren inte är det.
Krav på TLS
fremforge levererar bara webhooks över HTTPS. Mottagar-URL:er med HTTP nekas redan när webhooken konfigureras. Mottagaren måste:
- Tillhandahålla ett giltigt TLS-certifikat för det konfigurerade värdnamnet (alla publika CA:er fungerar: Let’s Encrypt, ZeroSSL, ACM, kommersiella).
- Stödja TLS 1.2 eller senare.
- Inte förlita sig på SNI-knep som bara fungerar för webbläsare; fremforges utgående TLS beter sig som en HTTP-klient, inte som en webbläsare.
Självsignerade certifikat nekas. Om du testar lokalt, använd i stället en tunnel (ngrok, Cloudflare Tunnel, Bunny Origin Shield) med ett riktigt certifikat.
Omförsök
fremforge räknar bara ett 2xx-svar som levererat. Alla andra svar leder till omförsök: 3xx (omdirigeringar följs inte), 4xx, 5xx, en timeout (10 sekunder) eller ett anslutnings- eller TLS-fel. Svara 200 för att stoppa omförsök för en händelse du inte bryr dig om. Spara 4xx till felformaterat innehåll, och räkna med att även de försöks igen.
Schema för omförsök: sex försök totalt. Efter det första försöket kommer omförsöken 2 minuter, 5 minuter, 15 minuter, 1 timme och 6 timmar efter det föregående försöket, sammanlagt ungefär 7,5 timmar. Efter det sjätte misslyckade försöket markeras leveransen som dlq i leveransloggen för webhooks i organisationens administrationsgränssnitt och försöks inte igen. Efter fem misslyckade leveranser i rad får organisationens faktureringskontakter ett mejl som namnger mottagaren.
För att spela upp en misslyckad leverans manuellt använder du knappen Skicka om i leveransloggen för webhooks i organisationens administrationsgränssnitt. Det finns ingen REST-endpoint för det.
Konfigurera en webhook
Webhookkonfigurationen finns på två ställen beroende på vad du vill göra:
- Utgående distribution från fremforge till din endpoint: organisationens administration, Integration, Webhooks (
https://frem.sh/<your-org>/_admin/webhooks). Lägg till mottagare, signeringssecret och händelsefilter; här ser du också leveranshälsan och DLQ:n. - Forgejos egna hooks per repo: Forgejos eget gränssnitt på
https://frem.sh/<your-org>/<repo>/settings/hooks. Forgejos sida på organisationsnivå,https://frem.sh/org/<your-org>/settings/hooks, skickar dig vidare till fliken Webhooks ovan. Det här är de äldre Forgejo-hooksen. fremforges instansövergripande systemhook fångar samma händelser centralt, så de flesta kunder behöver inte längre koppla upp hooks per repo.
Så lägger du till en utgående mottagare som fremforge hanterar:
- Organisationens administration, Integration, Webhooks, Lägg till en mottagare.
- Mål-URL: måste vara
https://. - Content type:
application/jsonrekommenderas. - Secret: generera ett värde med hög entropi (≥ 32 byte) och lagra det i mottagarens secret-hanterare. Återanvänd inte en secret mellan webhooks. Olika mottagare ska använda olika secrets.
- Händelser: välj vilka händelser som utlöser webhooken.
- Spara.
Webhookens secret lagras krypterad i vila i fremforges databas. fremforge visar den aldrig igen via API:t efter att den har skapats; du roterar den genom att redigera webhooken och ange ett nytt värde (det gamla värdet ersätts atomärt).
En mottagare kan också ta emot säkerhetshändelser: nya fynd, träffar på skadlig kod, blockerade pulls, policyöverträdelser och utgångna riskaccepteranden. De väljs till uttryckligen med namn och kan levereras som ServiceNow-incidenter. Se Webhooks för säkerhetslarm.
Felsökning
Signaturer som inte stämmer
De flesta signaturer som inte stämmer beror på att kroppen serialiseras om. Vanliga orsaker:
- JSON-middlewaren
body-parseri Express körs FÖRE din verifiering, så att verifieringen ser ett omserialiserat innehåll i stället för de byte fremforge skickade. Lösning: fånga den råa kroppen före tolkningen (express.raw()och tolka sedan JSON manuellt). - Ramverk som ”hjälpsamt” formaterar eller kanoniserar JSON innan de lämnar ut den: samma lösning.
- Skillnader i teckenkodning: fremforge skickar UTF-8, och mottagare som tolkar det som Latin-1 misslyckas.
TLS-fel i leveransloggen
unable to get local issuer certificate: din TLS-kedja är ofullständig; skicka mellanliggande certifikat tillsammans med lövcertifikatet.certificate has expired: förnya certifikatet och klicka sedan på Skicka om.tls: handshake failure: TLS-versionen eller chiffersviterna matchar inte; fremforges utgående trafik stöder bara moderna chiffersviter.
Uppspelningar / dubbla sidoeffekter
Om din mottagare inte är idempotent och inte håller reda på X-Gitea-Delivery kan kortvariga dubbletter uppstå vid omförsök (t.ex. när fremforge levererar en webhook igen för att ditt 200-svar gick förlorat på vägen). Åtgärda det genom att lägga till uppspelningscachen som beskrivs ovan.
Källa
Signeringsimplementationen är Forgejos egen från upstream; fremforge patchar den inte. Se Forgejos dokumentation om webhooks för fler detaljer om händelsernas format. fremforges roll är att konfigurera secrets med hög entropi som standard och att tillhandahålla verifieringsrecepten som beskrivs här.
För säkerheten kring inkommande webhooks (Mollie-betalningar → fremforge), se säkerhetsmodellen. Den använder ett annat mönster: URL:en som secret.