FireShield
← Toute la documentation

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 thresholdPercent dans 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 toujours 100, 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. null pour 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. null pour un pic d'utilisation.
  • currency: Code ISO 4217 des deux montants, par ex. "USD". null pour 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 le multiplier de 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 2xx une 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"
Logo FireWand

Vous gérez une base de données Firebase Data Connect ?

FireWand vous permet de parcourir, filtrer et modifier vos tables avec une vraie console SQL, gratuitement pour votre premier projet.

Essayer FireWand →