~/keydrop/tools/webhooks

Genera un kit completo de variables y snippets para firmar y verificar webhooks.

Webhook security kit

vars

Variables sugeridas

Los valores se generan en tu navegador con Web Crypto.

  • secWEBHOOK_SECRET

    Secreto principal para firmar/verificar peticiones.

    whsec_YhkqbU1ls0Ixy1wsGQ7eqXU7zh5W4XnrBVtmSiPKMNfey6bO
  • secWEBHOOK_SIGNING_SECRET

    Variante explícita para integraciones tipo Stripe (whsec_…).

    whsec_FxMuBLCdw0R64SdFc6BfhxHSPUAJL8550gzJbprw7LF7NfNY
  • secHMAC_SECRET

    Secreto HMAC genérico cuando hay varios consumidores.

    whsec_HmBQxertEbnjBgw4DDqJcMiA17PMx-3e8a6a-sTiO-DLhgeJ
  • secINTERNAL_WEBHOOK_TOKEN

    Token interno para webhooks entre servicios privados.

    whsec_rqqP3rOBRLjIlF_IO4t-R7lSD7L60AMRCx6IbffVvdFkZVEb
  • numWEBHOOK_TIMESTAMP_TOLERANCE_SECONDS

    Ventana en segundos para aceptar firmas con timestamp. Recomendado 300.

    300
  • numWEBHOOK_REPLAY_WINDOW_SECONDS

    Tiempo máximo de cache de IDs de webhooks recibidos para detectar replays.

    600
snippets

Snippets

// app/api/webhook/route.ts
import { NextResponse } from 'next/server';
import crypto from 'node:crypto';

const SECRET = process.env.WEBHOOK_SECRET ?? '';
const TOLERANCE = Number(process.env.WEBHOOK_TIMESTAMP_TOLERANCE_SECONDS ?? 300);

export async function POST(request: Request) {
  const ts = request.headers.get('x-webhook-timestamp');
  const sig = request.headers.get('x-webhook-signature');
  if (!ts || !sig) return NextResponse.json({ error: 'missing_headers' }, { status: 400 });

  const skew = Math.abs(Math.floor(Date.now() / 1000) - Number(ts));
  if (!Number.isFinite(skew) || skew > TOLERANCE) {
    return NextResponse.json({ error: 'expired' }, { status: 400 });
  }

  const raw = await request.text();
  const expected = crypto.createHmac('sha256', SECRET).update(`${ts}.${raw}`).digest('hex');
  const sigBuf = Buffer.from(sig, 'hex');
  const expBuf = Buffer.from(expected, 'hex');
  if (sigBuf.length !== expBuf.length || !crypto.timingSafeEqual(sigBuf, expBuf)) {
    return NextResponse.json({ error: 'invalid_signature' }, { status: 401 });
  }

  // payload válido: procesa el evento aquí
  return NextResponse.json({ ok: true });
}
warnings

Recomendaciones

  • !No pases secretos por query params, usa cabeceras.
  • !Valida siempre el timestamp y rechaza eventos antiguos.
  • !Usa comparación timing-safe (`crypto.timingSafeEqual` en Node).
  • !No loguees el cuerpo del webhook ni la firma cruda.
  • !Rota el secreto periódicamente y permite ventana doble durante la rotación.
  • !Restringe los webhooks a IPs del proveedor cuando sea posible.
length

Longitud del secreto

48
format

Formato

prefix

Prefijo

numeric

Tolerancias