Webhooks Soft Kill
Dernière mise à jour : November 2026
Qu'est-ce qu'un Soft Kill
Un Soft Kill permet à votre propre service de se dégrader avant que la dépense ne devienne incontrôlable (désactiver une fonctionnalité coûteuse, réduire le nombre de workers, afficher une page de maintenance) sans couper la facturation. Lorsqu'un seuil de budget auquel vous l'avez rattaché est franchi, FireShield envoie une requête POST signée à un endpoint HTTPS que vous contrôlez. La suite dépend entièrement de votre code.
Si vous avez plutôt besoin d'un arrêt garanti, utilisez un Hard Kill, qui dissocie le compte de facturation du projet.
1. Le configurer dans FireShield
- Ouvrez le budget de votre projet (Configurer) et, sur le seuil voulu, choisissez + Ajouter une action → Soft Kill (webhook).
- Saisissez l'URL de votre endpoint. Elle doit utiliser HTTPS.
- FireShield génère un secret de signature pour cet endpoint. Cliquez sur Afficher la clé secrète sur l'action pour l'afficher, et stockez-le comme secret dans votre service (par exemple
FIRESHIELD_WEBHOOK_SECRET). S'il fuite, cliquez sur Régénérer au même endroit : l'ancien secret cesse immédiatement de fonctionner, mettez donc votre service à jour avec le nouveau. - Cliquez sur Enregistrer pour que le budget se synchronise. Un endpoint peut être réutilisé sur d'autres seuils du même projet.
- Vous voulez dégrader progressivement ? Ajoutez un Soft Kill à plusieurs seuils (par exemple 50 %, 80 % et 100 %) et testez
thresholdPercentdans votre handler.
2. La requête
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"
}En-têtes :
X-FireShield-Timestamp: Heure Unix en secondes au moment de la signature de la requête.X-FireShield-Signature: HMAC-SHA256 en hexadécimal de{timestamp}.{raw body}, calculé avec votre secret de signature.
Corps (JSON) :
event:"soft_kill"lorsqu'un seuil est franchi, ou"test"lorsque vous cliquez sur Envoyer un test dans FireShield (voir ci-dessous). Ignorez les événements que vous ne reconnaissez pas ; répondez simplement 2xx.cloudProjectId: L'ID du projet chez FireShield, et non l'ID de votre projet GCP. Utilisez-le pour distinguer les projets si un même endpoint en sert plusieurs.gcpProjectId: L'ID de votre projet Google Cloud.trigger:"BUDGET"lorsqu'un seuil de budget s'est déclenché,"ANOMALY"lorsqu'une règle de pic d'utilisation s'est déclenchée (Pro).thresholdPercent: Le seuil de budget déclenché, en pourcentage (par ex.90). Chaque seuil doté d'un Soft Kill envoie sa propre requête ; utilisez cette valeur pour décider jusqu'où dégrader. Pour un pic d'utilisation, elle vaut toujours100, de sorte qu'un handler qui dégrade par paliers applique sa dégradation la plus forte.costAmount: La dépense depuis le début de la période budgétaire, telle que signalée par Google.nullpour un pic d'utilisation : la détection d'anomalies mesure l'utilisation, pas les coûts.budgetAmount: Le montant du budget pour la période.nullpour un pic d'utilisation.currency: Code ISO 4217 des deux montants, par ex."USD".nullpour un pic d'utilisation.anomaly: Uniquement pour un pic d'utilisation :service(par ex."Firestore"),metric,current(utilisation sur la dernière heure),baseline(utilisation horaire moyenne sur les 7 derniers jours) et lemultiplierde la règle. Utilisez-le pour ne dégrader que le service concerné par le pic.reason: Une phrase lisible destinée à vos journaux, par exemple le seuil atteint et la dépense. Ne l'analysez pas : sa formulation peut changer.timestamp: Même valeur que l'en-tête, sous forme de chaîne.
Un Soft Kill envoyé par une règle de pic d'utilisation ressemble à ceci :
{
"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. La vérifier
Avant d'agir sur une requête, vérifiez ces deux points et rejetez-la avec un 401 dans le cas contraire :
- Fraîcheur : l'horodatage est à moins de 5 minutes de votre horloge. Cela empêche qu'une requête interceptée soit rejouée plus tard.
- Signature : recalculez le HMAC sur l'horodatage, un point et le corps brut de la requête, exactement tel que reçu, puis comparez-le à l'en-tête en temps constant. Re-sérialiser du JSON parsé modifie les octets et invalide la signature.
// 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. Répondre
Répondez avec n'importe quel statut 2xx dès que vous avez accepté la requête ; effectuez les traitements longs de manière asynchrone. Tout autre statut, ou une erreur réseau, est enregistré comme une action en échec et apparaît dans les journaux récents de votre projet.
5. Garanties de livraison
- Chaque action Soft Kill se déclenche une fois par période budgétaire (mois calendaire), la première fois que la dépense signalée par Google franchit son seuil. Modifier le montant du budget la réarme, et elle se déclenche à nouveau lorsque le nouveau seuil est franchi.
- Les livraisons en échec sont relancées, mais pas depuis une file d'attente propre : chaque requête est une tentative unique qui attend jusqu'à 15 secondes, sans nouvelle tentative immédiate ni backoff. La tentative suivante arrive avec la donnée suivante. Pour un budget, c'est la prochaine mise à jour des coûts de Google (en général toutes les demi-heures environ). Pour un pic d'utilisation, c'est la vérification suivante (toutes les 5 minutes), et seulement tant que l'utilisation respecte encore la règle : un pic qui se termine avant qu'une livraison réussisse n'est pas renvoyé. Répondez par un
2xxune fois la requête traitée, sinon vous continuerez à la recevoir. - Google publie les données de coûts avec un délai pouvant atteindre plusieurs heures ; la requête arrive donc un certain temps après que la dépense a réellement franchi le seuil.
- Traitez quand même la requête de façon idempotente : activer deux fois le mode dégradé doit être sans conséquence. Un pic d'utilisation qui dure la renvoie aussi à chaque analyse.
6. Tester votre endpoint
Le plus rapide : dans le budget de votre projet, cliquez sur Envoyer un test sous l'action Soft Kill. FireShield envoie une requête signée identique à une vraie, avec le thresholdPercent de ce seuil et event défini sur "test", de sorte qu'un handler qui n'agit que sur "soft_kill" ne dégradera rien. Vous savez immédiatement si votre endpoint est injoignable, rejette la signature ou ne répond pas 2xx.
Ou envoyez-vous une requête correctement signée avec le secret que FireShield vous a fourni :
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"