FireShield
← Toda a documentação

Webhooks de Soft Kill

Última atualização: November 2026

O que é um Soft Kill

Um Soft Kill permite que seu próprio serviço se degrade antes que o gasto saia do controle (desligar um recurso caro, reduzir workers, mudar para uma página de manutenção) sem cortar o faturamento. Quando um limite de orçamento ao qual você o associou é ultrapassado, o FireShield envia uma requisição POST assinada para um endpoint HTTPS que você controla. O que acontece depois depende inteiramente do seu código.

Se você precisa de uma parada garantida, use um Hard Kill, que desconecta a conta de faturamento do projeto.

1. Configure no FireShield

  • Abra o orçamento do seu projeto (Configurar) e, no limite desejado, escolha + Adicionar ação → Soft Kill (webhook).
  • Informe a URL do seu endpoint. Ela precisa usar HTTPS.
  • O FireShield gera um segredo de assinatura para ele. Clique em Ver chave secreta na ação para vê-lo e armazene-o como segredo no seu serviço (por exemplo, FIRESHIELD_WEBHOOK_SECRET). Se ele vazar, clique em Gerar novamente ali: o segredo antigo para de funcionar na hora, então atualize seu serviço com o novo.
  • Clique em Salvar para que o orçamento seja sincronizado. Um endpoint pode ser reutilizado em outros limites do mesmo projeto.
  • Quer degradar passo a passo? Adicione um Soft Kill a vários limites (por exemplo, 50%, 80% e 100%) e use thresholdPercent no seu handler para decidir o que fazer.

2. A requisição

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

Headers:

  • X-FireShield-Timestamp: Hora Unix, em segundos, em que a requisição foi assinada.
  • X-FireShield-Signature: HMAC-SHA256 em hexadecimal de {timestamp}.{raw body}, usando seu segredo de assinatura como chave.

Corpo (JSON):

  • event: "soft_kill" quando um limite é ultrapassado, ou "test" quando você clica em Enviar teste no FireShield (veja abaixo). Ignore eventos que você não reconhece; apenas responda 2xx.
  • cloudProjectId: O ID do projeto no FireShield, não o ID do seu projeto do GCP. Use-o para diferenciar projetos se um endpoint atender vários.
  • gcpProjectId: O ID do seu projeto do Google Cloud.
  • trigger: "BUDGET" quando um limite de orçamento disparou, "ANOMALY" quando uma regra de pico de uso disparou (Pro).
  • thresholdPercent: O limite de orçamento que disparou, como porcentagem (por exemplo, 90). Cada limite com Soft Kill envia sua própria requisição, então use-o para decidir o quanto degradar. Para um pico de uso, é sempre 100, de modo que um handler que degrada por limite aplica sua degradação mais forte.
  • costAmount: Gasto até agora neste período do orçamento, conforme informado pelo Google. null para um pico de uso: a detecção de anomalias mede uso, não custo.
  • budgetAmount: O valor do orçamento para o período. null para um pico de uso.
  • currency: Código ISO 4217 dos dois valores, por exemplo, "USD". null para um pico de uso.
  • anomaly: Apenas para um pico de uso: service (por exemplo, "Firestore"), metric, current (uso na última hora), baseline (uso médio por hora nos últimos 7 dias) e o multiplier da regra. Use-o para degradar apenas o serviço que teve o pico.
  • reason: Uma frase legível para seus logs, por exemplo, qual limite foi atingido e o gasto. Não a interprete no código; o texto pode mudar.
  • timestamp: O mesmo valor do header, como string.

Um Soft Kill enviado por uma regra de pico de uso tem esta aparência:

{
  "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. Verifique

Antes de agir com base em uma requisição, verifique os dois itens abaixo e rejeite-a com um 401 caso contrário:

  • Atualidade: o timestamp está a no máximo 5 minutos do seu relógio. Isso impede que uma requisição capturada seja reenviada depois.
  • Assinatura: recalcule o HMAC sobre o timestamp, um ponto e o corpo bruto da requisição, exatamente como recebido, e compare-o com o header em tempo constante. Serializar de novo o JSON já interpretado altera os bytes e invalida a assinatura.
// 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. Responda

Responda com qualquer status 2xx assim que aceitar a requisição; faça o trabalho demorado de forma assíncrona. Qualquer outro status, ou um erro de rede, é registrado como ação com falha e aparece nos logs recentes do seu projeto.

5. Garantias de entrega

  • Cada ação de Soft Kill dispara uma vez por período do orçamento (mês do calendário), na primeira vez que o gasto informado pelo Google ultrapassa o seu limite. Alterar o valor do orçamento a rearma, então ela dispara de novo quando o novo limite for ultrapassado.
  • Entregas com falha são repetidas, mas não a partir de uma fila própria: cada requisição é uma única tentativa que espera até 15 segundos, sem novas tentativas imediatas nem backoff. A próxima tentativa vem com o próximo dado. Em um orçamento, é a próxima atualização de custos do Google (normalmente a cada meia hora, mais ou menos). Em um pico de uso, é a próxima verificação (a cada 5 minutos), e só enquanto o uso ainda cumprir a regra, então um pico que termina antes de uma entrega dar certo não é enviado de novo. Responda com um 2xx depois de tratar a requisição, ou você vai continuar recebendo-a.
  • O Google publica os dados de custo com um atraso de até várias horas, então a requisição chega algum tempo depois de o gasto ter de fato ultrapassado o limite.
  • Trate a requisição de forma idempotente mesmo assim: ativar o modo degradado duas vezes deve ser inofensivo. Um pico de uso que se prolonga também a reenvia a cada verificação.

6. Teste seu endpoint

O jeito mais rápido: no orçamento do seu projeto, clique em Enviar teste na ação de Soft Kill. O FireShield envia uma requisição assinada exatamente como uma real, com o thresholdPercent daquele limite e event definido como "test", para que um handler que só age com "soft_kill" não degrade nada. Ele avisa na hora se seu endpoint não pode ser acessado, rejeita a assinatura ou não responde 2xx.

Ou envie para você mesmo uma requisição assinada corretamente com o segredo que o FireShield forneceu:

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

Gerencia um banco de dados do Firebase Data Connect?

O FireWand permite navegar, filtrar e editar suas tabelas com um console SQL de verdade, grátis para o seu primeiro projeto.

Experimente o FireWand →