Webhook Soft Kill
Ultimo aggiornamento: November 2026
Cos'è un Soft Kill
Un Soft Kill permette al tuo servizio di degradarsi da solo prima che la spesa sfugga di mano (disattivare una funzionalità costosa, ridurre i worker, passare a una pagina di manutenzione) senza interrompere la fatturazione. Quando viene superata una soglia di budget a cui l'hai associato, FireShield invia una richiesta POST firmata a un endpoint HTTPS che controlli tu. Cosa succede dopo dipende interamente dal tuo codice.
Se invece ti serve un arresto garantito, usa un Hard Kill, che scollega l'account di fatturazione del progetto.
1. Configuralo in FireShield
- Apri il budget del tuo progetto (Configura) e, sulla soglia che vuoi, scegli + Aggiungi azione → Soft Kill (webhook).
- Inserisci l'URL del tuo endpoint. Deve usare HTTPS.
- FireShield genera un segreto di firma per esso. Premi Visualizza chiave segreta sull'azione per vederlo, e conservalo come segreto nel tuo servizio (ad esempio
FIRESHIELD_WEBHOOK_SECRET). Se dovesse trapelare, premi Rigenera lì: il vecchio segreto smette subito di funzionare, quindi aggiorna il tuo servizio con quello nuovo. - Fai clic su Salva per sincronizzare il budget. Un endpoint può essere riutilizzato su altre soglie dello stesso progetto.
- Vuoi degradare per gradi? Aggiungi un Soft Kill a più soglie (ad esempio 50%, 80% e 100%) e distingui in base a
thresholdPercentnel tuo handler.
2. La richiesta
POST /fireshield/soft-kill HTTP/1.1
Content-Type: application/json
X-FireShield-Timestamp: 1790899412
X-FireShield-Signature: 5f2b1c…e9a0
{
"event": "soft_kill",
"cloudProjectId": "e3e0cc6f0146409c8d54ce43c0757294",
"gcpProjectId": "firewand-prod",
"thresholdPercent": 90,
"costAmount": 90.12,
"budgetAmount": 100,
"currency": "USD",
"reason": "FireWand hit 90% of budget (90.12 / 100 USD)",
"trigger": "BUDGET",
"timestamp": "1790899412"
}Header:
X-FireShield-Timestamp: Ora Unix in secondi in cui la richiesta è stata firmata.X-FireShield-Signature: HMAC-SHA256 esadecimale di{timestamp}.{raw body}, con il tuo segreto di firma come chiave.
Corpo (JSON):
event:"soft_kill"quando una soglia viene superata, oppure"test"quando premi Invia test in FireShield (vedi sotto). Ignora gli eventi che non riconosci; rispondi semplicemente 2xx.cloudProjectId: L'ID del progetto in FireShield, non l'ID del tuo progetto GCP. Usalo per distinguere i progetti se un endpoint ne serve diversi.gcpProjectId: L'ID del tuo progetto Google Cloud.trigger:"BUDGET"quando è scattata una soglia di budget,"ANOMALY"quando è scattata una regola sui picchi di utilizzo (Pro).thresholdPercent: La soglia di budget che è scattata, come percentuale (ad es.90). Ogni soglia con un Soft Kill invia la propria richiesta, quindi usala per decidere quanto degradare. Per un picco di utilizzo è sempre100, così un handler che degrada per soglia applica il suo livello di degrado più forte.costAmount: La spesa finora in questo periodo di budget, come comunicata da Google.nullper un picco di utilizzo: il rilevamento delle anomalie misura l'utilizzo, non il costo.budgetAmount: L'importo del budget per il periodo.nullper un picco di utilizzo.currency: Codice ISO 4217 di entrambi gli importi, ad es."USD".nullper un picco di utilizzo.anomaly: Solo per un picco di utilizzo:service(ad es."Firestore"),metric,current(utilizzo nell'ultima ora),baseline(utilizzo orario medio negli ultimi 7 giorni) e ilmultiplierdella regola. Usalo per degradare solo il servizio che ha avuto il picco.reason: Una frase leggibile per i tuoi log, ad esempio quale soglia è stata raggiunta e la spesa. Non analizzarla; il testo può cambiare.timestamp: Stesso valore dell'header, come stringa.
Un Soft Kill inviato da una regola sui picchi di utilizzo ha questo aspetto:
{
"event": "soft_kill",
"trigger": "ANOMALY",
"cloudProjectId": "e3e0cc6f0146409c8d54ce43c0757294",
"gcpProjectId": "firewand-prod",
"thresholdPercent": 100,
"costAmount": null,
"budgetAmount": null,
"currency": null,
"anomaly": {
"service": "Cloud Firestore",
"metric": "firestore.reads",
"current": 1200000,
"baseline": 100000,
"multiplier": 10
},
"reason": "FireWand: Firestore document reads at ×12 normal (1.2M document reads in the last hour, usually 100K document reads). Rule: ×10.",
"timestamp": "1790899412"
}3. Verificala
Prima di agire su una richiesta, controlla entrambi questi punti e, in caso contrario, rifiutala con un 401:
- Freschezza: il timestamp rientra in 5 minuti rispetto al tuo orologio. Questo impedisce che una richiesta intercettata venga riprodotta in seguito.
- Firma: ricalcola l'HMAC sul timestamp, un punto e il corpo grezzo della richiesta esattamente come ricevuto, poi confrontalo con l'header a tempo costante. Riserializzare il JSON analizzato cambia i byte e invalida la firma.
// Firebase Cloud Function (2nd gen). The same logic works in Express:
// sign over the raw body bytes, never over a re-serialized JSON object.
import { onRequest } from "firebase-functions/v2/https";
import { createHmac, timingSafeEqual } from "node:crypto";
const SECRET = process.env.FIRESHIELD_WEBHOOK_SECRET!;
const MAX_AGE_SECONDS = 5 * 60;
export const fireshieldSoftKill = onRequest(async (req, res) => {
const timestamp = req.get("X-FireShield-Timestamp") ?? "";
const signature = req.get("X-FireShield-Signature") ?? "";
const rawBody = req.rawBody.toString("utf8");
// 1. Reject stale or replayed requests.
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!timestamp || Number.isNaN(age) || age > MAX_AGE_SECONDS) {
res.status(401).send("Stale request");
return;
}
// 2. Recompute the signature and compare in constant time.
const expected = createHmac("sha256", SECRET).update(`${timestamp}.${rawBody}`).digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(signature, "hex");
if (a.length !== b.length || !timingSafeEqual(a, b)) {
res.status(401).send("Bad signature");
return;
}
// 3. Degrade your service, then answer fast with a 2xx.
const event = JSON.parse(rawBody);
if (event.event === "soft_kill") {
if (event.trigger === "ANOMALY") {
// A usage spike in one service. thresholdPercent is always 100 here,
// so degrade only the service in event.anomaly.service (for example
// "Cloud Firestore" or "Cloud Storage"), not the whole app.
await degradeService(event.anomaly.service); // your code
} else {
// "BUDGET": each threshold with a Soft Kill sends its own request,
// so you can step down gradually as more of the budget is spent.
if (event.thresholdPercent >= 100) await showMaintenancePage(); // your code
else if (event.thresholdPercent >= 80) await disableExpensiveFeatures();
else await pauseBatchJobs();
}
}
res.status(204).send();
});4. Rispondi
Rispondi con qualsiasi stato 2xx non appena hai accettato la richiesta; esegui il lavoro lento in modo asincrono. Qualsiasi altro stato, o un errore di rete, viene registrato come azione fallita e compare nei Log recenti del tuo progetto.
5. Garanzie di consegna
- Ogni azione Soft Kill scatta una volta per periodo di budget (mese di calendario), la prima volta che la spesa comunicata da Google supera la sua soglia. Modificare l'importo del budget la riattiva, quindi scatta di nuovo quando viene superata la nuova soglia.
- Le consegne fallite vengono ritentate, ma non da una coda propria: ogni richiesta è un singolo tentativo che attende fino a 15 secondi, senza nuovi tentativi immediati né backoff. Il tentativo successivo arriva con il dato successivo. Per un budget, è il prossimo aggiornamento dei costi di Google (di solito ogni mezz'ora circa). Per un picco di utilizzo, è il controllo successivo (ogni 5 minuti), e solo finché l'utilizzo rispetta ancora la regola: un picco che finisce prima che una consegna vada a buon fine non viene inviato di nuovo. Rispondi con un
2xxdopo aver gestito la richiesta, altrimenti continuerai a riceverla. - Google pubblica i dati di costo con un ritardo fino a diverse ore, quindi la richiesta arriva un po' dopo che la spesa ha effettivamente superato la soglia.
- Gestisci comunque la richiesta in modo idempotente: attivare due volte la modalità degradata non dovrebbe causare problemi. Un picco di utilizzo che si prolunga la reinvia anche a ogni controllo.
6. Testa il tuo endpoint
Il modo più rapido: nel budget del tuo progetto, premi Invia test sotto l'azione Soft Kill. FireShield invia una richiesta firmata identica a una reale, con il thresholdPercent di quella soglia e event impostato a "test", così un handler che agisce solo su "soft_kill" non degraderà nulla. Ti dice subito se il tuo endpoint non è raggiungibile, rifiuta la firma o non risponde 2xx.
Oppure inviati da solo una richiesta firmata correttamente con il segreto che FireShield ti ha fornito:
SECRET='your-signing-secret'
TS=$(date +%s)
BODY='{"event":"soft_kill","cloudProjectId":"test","gcpProjectId":"test","thresholdPercent":90,"costAmount":90,"budgetAmount":100,"currency":"USD","reason":"Manual test","timestamp":"'$TS'"}'
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')
curl -i -X POST https://your-app.example.com/fireshield/soft-kill \
-H "Content-Type: application/json" \
-H "X-FireShield-Timestamp: $TS" \
-H "X-FireShield-Signature: $SIG" \
--data "$BODY"