FireShield
← Toda la documentación

Webhooks de Soft Kill

Última actualización: November 2026

Qué es un Soft Kill

Un Soft Kill permite que tu propio servicio se degrade antes de que el gasto se descontrole (desactivar una función cara, reducir los workers, cambiar a una página de mantenimiento) sin cortar la facturación. Cuando se supera un umbral de presupuesto al que lo asociaste, FireShield envía una única petición POST firmada a un endpoint HTTPS que controlas. Lo que pase a continuación depende por completo de tu código.

Si lo que necesitas es una parada garantizada, usa un Hard Kill, que desconecta la cuenta de facturación del proyecto.

1. Configúralo en FireShield

  • Abre el presupuesto de tu proyecto (Configurar) y, en el umbral que quieras, elige + Añadir acción → Soft Kill (webhook).
  • Introduce la URL de tu endpoint. Debe usar HTTPS.
  • FireShield genera un secreto de firma para él. Pulsa Ver clave secreta en la acción para verlo y guárdalo como secreto en tu servicio (por ejemplo FIRESHIELD_WEBHOOK_SECRET). Si alguna vez se filtra, pulsa Regenerar ahí mismo: el secreto anterior deja de funcionar al instante, así que actualiza tu servicio con el nuevo.
  • Haz clic en Guardar para que el presupuesto se sincronice. Un endpoint puede reutilizarse en otros umbrales del mismo proyecto.
  • ¿Quieres degradar paso a paso? Añade un Soft Kill a varios umbrales (por ejemplo 50%, 80% y 100%) y ramifica según thresholdPercent en tu handler.

2. La petición

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"
}

Cabeceras:

  • X-FireShield-Timestamp: Hora Unix en segundos en la que se firmó la petición.
  • X-FireShield-Signature: HMAC-SHA256 en hexadecimal de {timestamp}.{raw body}, con tu secreto de firma como clave.

Cuerpo (JSON):

  • event: "soft_kill" cuando se supera un umbral, o "test" cuando pulsas Enviar prueba en FireShield (ver más abajo). Ignora los eventos que no reconozcas; simplemente responde 2xx.
  • cloudProjectId: El ID del proyecto en FireShield, no el ID de tu proyecto de GCP. Úsalo para distinguir proyectos si un endpoint sirve a varios.
  • gcpProjectId: El ID de tu proyecto de Google Cloud.
  • trigger: "BUDGET" cuando saltó un umbral de presupuesto, "ANOMALY" cuando saltó una regla de picos de uso (Pro).
  • thresholdPercent: El umbral de presupuesto que saltó, como porcentaje (p. ej. 90). Cada umbral con un Soft Kill envía su propia petición, así que úsalo para decidir cuánto degradar. En un pico de uso siempre es 100, de modo que un handler que degrada por umbrales aplica su degradación más fuerte.
  • costAmount: El gasto en lo que va de periodo del presupuesto, tal como lo informó Google. null en un pico de uso: la detección de anomalías mide uso, no coste.
  • budgetAmount: El importe del presupuesto para el periodo. null en un pico de uso.
  • currency: Código ISO 4217 de ambos importes, p. ej. "USD". null en un pico de uso.
  • anomaly: Solo en un pico de uso: service (p. ej. "Firestore"), metric, current (uso en la última hora), baseline (uso medio por hora en los últimos 7 días) y el multiplier de la regla. Úsalo para degradar solo el servicio que tuvo el pico.
  • reason: Una frase legible para tus registros, p. ej. qué umbral se alcanzó y el gasto. No la analices; su redacción puede cambiar.
  • timestamp: El mismo valor que la cabecera, como cadena.

Un Soft Kill enviado por una regla de picos de uso tiene este aspecto:

{
  "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. Verifícala

Antes de actuar sobre una petición, comprueba estas dos cosas y, si no se cumplen, recházala con un 401:

  • Vigencia: la marca de tiempo está a menos de 5 minutos de tu reloj. Así se evita que una petición capturada se reenvíe más tarde.
  • Firma: recalcula el HMAC sobre la marca de tiempo, un punto y el cuerpo de la petición en bruto, exactamente como se recibió, y compáralo con la cabecera en tiempo constante. Volver a serializar el JSON ya parseado cambia los bytes y rompe 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. Responde

Responde con cualquier estado 2xx en cuanto hayas aceptado la petición; haz el trabajo lento de forma asíncrona. Cualquier otro estado, o un error de red, se registra como acción fallida y aparece en los registros recientes de tu proyecto.

5. Garantías de entrega

  • Cada acción de Soft Kill se dispara una vez por periodo de presupuesto (mes natural), la primera vez que el gasto que informa Google supera su umbral. Cambiar el importe del presupuesto la rearma, así que vuelve a dispararse cuando se supera el nuevo umbral.
  • Las entregas fallidas se reintentan, pero no desde una cola propia: cada petición es un único intento que espera hasta 15 segundos, sin reintentos inmediatos ni backoff. El siguiente intento llega con el siguiente dato. En un presupuesto, es la siguiente actualización de costes de Google (normalmente más o menos cada media hora). En un pico de uso, es la siguiente comprobación (cada 5 minutos), y solo mientras el uso siga cumpliendo la regla, así que un pico que termina antes de que una entrega tenga éxito no se vuelve a enviar. Responde con un 2xx cuando hayas gestionado la petición, o la seguirás recibiendo.
  • Google publica los datos de costes con un retraso de hasta varias horas, así que la petición llega un tiempo después de que el gasto superara realmente el umbral.
  • Aun así, procesa la petición de forma idempotente: activar el modo degradado dos veces debería ser inofensivo. Un pico de uso que se prolonga también la reenvía en cada análisis.

6. Prueba tu endpoint

La forma más rápida: en el presupuesto de tu proyecto, pulsa Enviar prueba bajo la acción de Soft Kill. FireShield envía una petición firmada exactamente igual que una real, con el thresholdPercent de ese umbral y event con valor "test", así que un handler que solo actúa ante "soft_kill" no degradará nada. Te indica al momento si tu endpoint no es accesible, rechaza la firma o no responde 2xx.

O envíate tú mismo una petición correctamente firmada con el secreto que te dio FireShield:

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 de FireWand

¿Gestionas una base de datos de Firebase Data Connect?

FireWand te permite explorar, filtrar y editar tus tablas con una consola SQL de verdad, gratis para tu primer proyecto.

Prueba FireWand →