Webhooks
fremforge leverer webhooks som er kompatible med både Forgejos eget signaturskjema og det GitHub-kompatible skjemaet i samme levering. Mottakere som er bygget for det ene eller det andre, virker uten endringer. Velg headeren du stoler på, og verifiser mot den delte hemmeligheten.
Denne siden dokumenterer kontrakten: hva fremforge sender, hvordan du verifiserer det, og hvilke garantier du har for nye forsøk og TLS.
Hvor du konfigurerer hva: webhooks på organisasjonsnivå (hvilke hendelser det abonneres på, mål-URL, signeringshemmelighet) oppretter du i fremforge-administrasjonen på /<org>/_admin/webhooks, der du også finner leveringshistorikk og ny sending. Forgejos side for hooks på organisasjonsnivå, /org/<org>/settings/hooks, sender deg videre dit. Forgejo-hooks på repository-nivå (avgrenset til ett repository) setter du opp i Forgejos brukergrensesnitt på /<org>/<repo>/settings/hooks.
Hurtigreferanse
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 | Betydning |
|---|---|
X-Forgejo-Event | Hendelsestype (push, pull_request, issues, release osv.) |
X-Gitea-Delivery | Unik UUID per leveringsforsøk, brukes til beskyttelse mot gjentakelse |
X-Forgejo-Signature | HMAC-SHA256 av den rå bodyen, heksadesimalt kodet, Forgejos eget format |
X-Hub-Signature-256 | HMAC-SHA256 av den rå bodyen, heksadesimalt kodet med prefikset sha256=, GitHub-kompatibel |
X-Hub-Signature | HMAC-SHA1 av den rå bodyen, eldre GitHub-kompatibilitet, ikke stol på den |
Signeringsnøkkelen er hemmeligheten du setter på webhooken i administrasjonsgrensesnittet for organisasjonen. Tom hemmelighet = ingen signatur; X-*-Signature-headerne mangler. Plattformens standardpolicy i fremforge avviser webhook-konfigurasjon uten hemmelighet for nye organisasjoner (fase 1.5+).
Hvorfor to signaturheadere
Mottakere som er bygget for GitHub, sjekker som regel X-Hub-Signature-256. Mottakere som er bygget for Forgejo / Gitea, sjekker X-Forgejo-Signature. Når begge sendes i samme levering, krever ikke en migrering fra GitHub at du rører mottakerkoden; nye integrasjoner kan velge den formen som føles mest naturlig.
De to headerne har nøyaktig samme HMAC-SHA256-verdi, beregnet over den samme rå bodyen. Verifiser en av dem; lykkes begge, er forespørselen ekte.
Verifisere en webhook: oppskrifter
Alle oppskriftene nedenfor verifiserer den GitHub-kompatible headeren X-Hub-Signature-256. Tilpass til X-Forgejo-Signature ved å droppe steget som fjerner prefikset 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Å være nøyaktig de bytene som ble mottatt, før noen JSON-tolking. Mellomvare som serialiserer på nytt, gir en annen hash og et falskt negativt resultat.
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()
}Beskyttelse mot gjentakelse
HMAC-signaturen beviser ektheten (payloaden kom fra fremforge med kjennskap til den delte hemmeligheten), men ikke at den er fersk. En angriper som fanger opp en gyldig signert webhook, kan spille den av på nytt i det uendelige til hemmeligheten roteres.
Bruk UUID-en i X-Gitea-Delivery til å forsvare deg mot gjentakelse:
- Hold en liten buffer (Redis, minne, til og med SQLite) med nylige leverings-ID-er.
- Avvis alle innkommende webhooks der leverings-ID-en allerede ligger i bufferen.
- Sett TTL på bufferen til 24 timer. Alle nye forsøk fra fremforge skjer innen rundt 7,5 timer, så det vinduet dekker dem; eldre gjentakelser er ikke gjennomførbare gitt rotasjonsfrekvensen for hemmeligheten.
Dropp forsvaret mot gjentakelse bare hvis handleren din er fullt idempotent (samme levering brukt to ganger gir samme sluttilstand). Idempotente handlere er den ryddigste løsningen; en buffer mot gjentakelse er reserveløsningen når handleren ikke er det.
Krav til TLS
fremforge leverer bare webhooks over HTTPS. HTTP-URL-er for mottakere avvises når webhooken konfigureres. Mottakeren må:
- Levere et gyldig TLS-sertifikat for det konfigurerte vertsnavnet (alle offentlige CA-er, Let’s Encrypt, ZeroSSL, ACM, kommersielle, fungerer).
- Støtte TLS 1.2 eller høyere.
- Ikke være avhengig av SNI-triks som bare virker for nettlesere; fremforges utgående TLS oppfører seg som en HTTP-klient, ikke som en nettleser.
Selvsignerte sertifikater avvises. Hvis du tester lokalt, bruker du heller en tunnel (ngrok, Cloudflare Tunnel, Bunny Origin Shield) med et ekte sertifikat.
Oppførsel ved nye forsøk
fremforge regner bare et 2xx-svar som levert. Alle andre svar prøves på nytt: en 3xx (omdirigeringer følges ikke), en 4xx, en 5xx, et tidsavbrudd (10 sekunder) eller en tilkoblings- eller TLS-feil. For å stoppe nye forsøk for en hendelse du ikke bryr deg om, svarer du 200. Spar 4xx til payloads med feil format, og regn med at også de blir prøvd på nytt.
Plan for nye forsøk: seks forsøk totalt. Etter det første forsøket kommer nye forsøk 2 minutter, 5 minutter, 15 minutter, 1 time og 6 timer etter det forrige, rundt 7,5 timer totalt. Etter det sjette mislykkede forsøket merkes leveringen dlq i webhook-leveringsloggen i administrasjonsgrensesnittet for organisasjonen og prøves ikke igjen. Etter fem mislykkede leveringer på rad får organisasjonens faktureringskontakter en e-post som navngir målet.
For å levere en mislykket levering på nytt manuelt bruker du knappen Send på nytt i webhook-leveringsloggen i administrasjonsgrensesnittet for organisasjonen, eller kaller API-endepunktet POST /api/v1/repos/{owner}/{repo}/hooks/{hook_id}/redelivery/{delivery_id}.
Konfigurere en webhook
Webhook-konfigurasjonen ligger to steder, avhengig av hva du gjør:
- Utgående distribusjon fra fremforge til endepunktet ditt: organisasjonsadministrasjonen, Integration, Webhooks (
https://frem.sh/<your-org>/_admin/webhooks). Legg til målet, signeringshemmeligheten og hendelsesfilteret; her ser du også leveringshelse og DLQ. - Forgejos egne hooks per repository: Forgejos eget brukergrensesnitt på
https://frem.sh/<your-org>/<repo>/settings/hooks. Forgejos side på organisasjonsnivå,https://frem.sh/org/<your-org>/settings/hooks, sender deg videre til fanen Webhooks ovenfor. Dette er de eldre Forgejo-hookene. fremforges systemhook for hele instansen fanger de samme hendelsene sentralt, så de fleste kunder trenger ikke lenger å sette opp hooks per repository.
Slik legger du til et utgående mål som administreres av fremforge:
- Organisasjonsadministrasjonen, Integration, Webhooks, Add destination.
- Mål-URL, må være
https://. - Content type,
application/jsonanbefales. - Secret, generer en verdi med høy entropi (≥ 32 byte); lagre den i mottakerens hemmelighetsbehandler. Ikke gjenbruk en hemmelighet på tvers av webhooks. Ulike mottakere bør bruke ulike hemmeligheter.
- Hendelser, velg hvilke hendelser som utløser webhooken.
- Lagre.
Webhook-hemmeligheten lagres kryptert i fremforges database. fremforge viser den aldri tilbake via API-et etter at den er opprettet; roter den ved å redigere webhooken og skrive inn en ny verdi (den gamle verdien erstattes atomisk).
Et mål kan også motta sikkerhetshendelser: nye funn, treff på skadevare, blokkerte pulls, brudd på policyer og risikoaksepter som har utløpt. De må velges eksplisitt ved navn og kan leveres som ServiceNow-hendelser. Se Varselwebhooks for sikkerhet.
Feilsøking
Signaturer som ikke stemmer
De fleste signaturavvik skyldes at bodyen serialiseres på nytt. Vanlige syndere:
- Express’ JSON-mellomvare
body-parserkjører FØR verifiseringen din; verifiseringen ser da en ny serialisert payload, ikke de bytene fremforge sendte. Løsning: fang opp den rå bodyen før tolkingen (express.raw()og deretter JSON-tolking manuelt). - Rammeverk som «hjelpsomt» formaterer eller kanoniserer JSON før de gir den videre; samme løsning.
- Forskjeller i tegnkoding: fremforge sender UTF-8; mottakere som tolker som Latin-1, vil feile.
TLS-feil i leveringsloggen
unable to get local issuer certificate: TLS-kjeden din er ufullstendig; lever mellomsertifikatene sammen med løvsertifikatet.certificate has expired: forny, og klikk deretter på Send på nytt.tls: handshake failure: TLS-versjonen eller chifferet stemmer ikke; fremforges utgående trafikk støtter bare moderne chiffersett.
Gjentakelser / dupliserte bivirkninger
Hvis mottakeren din ikke er idempotent og ikke holder oversikt over X-Gitea-Delivery, kan det oppstå korte duplikater under nye forsøk (f.eks. når fremforge leverer en webhook på nytt fordi 200-svaret ditt gikk tapt underveis). Løs det ved å legge til bufferen mot gjentakelse som er beskrevet over.
Kilde
Signeringsimplementasjonen er Forgejos egen fra upstream; fremforge patcher den ikke. Se Forgejos dokumentasjon om webhooks for flere detaljer om formen på hendelsene. fremforges rolle er å sette opp hemmeligheter med høy entropi som standard og å gjøre verifiseringsoppskriftene som er dokumentert her, tilgjengelige.
For sikkerheten ved innkommende webhooks (Mollie-betalinger → fremforge), se sikkerhetsmodellen. Den bruker en annen form: URL-en som hemmelighet.