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
thresholdPercentno 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, é sempre100, 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.nullpara um pico de uso: a detecção de anomalias mede uso, não custo.budgetAmount: O valor do orçamento para o período.nullpara um pico de uso.currency: Código ISO 4217 dos dois valores, por exemplo,"USD".nullpara 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 omultiplierda 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
2xxdepois 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"