← Volver al blogRead in English
Integraciones

Verificar firmas de webhooks de Deepwick en Node, Python y Go

Verifica HMAC-SHA256 sobre el cuerpo original, rota secretos y evita procesar dos veces una alerta. Ejemplos en tres lenguajes.

Actualizado: 9 de octubre de 2026

Si configuras una URL de webhook en una alerta de Deepwick, la entrega utiliza una petición POST. El cuerpo se firma con HMAC-SHA256 y el secreto de esa alerta. La cabecera X-Deepwick-Signature tiene el formato sha256=<hex>.

Esta guía trata los webhooks de alertas. Los webhooks generales de cuenta utilizan un flujo de entrega distinto. La firma autentica el cuerpo; no sustituye la validación del evento ni el control de duplicados.

Qué envía Deepwick

Las cabeceras incluyen Content-Type: application/json, User-Agent: deepwick-alerts/1.0, X-Deepwick-Event: alert.fired, X-Deepwick-Event-Id, X-Deepwick-Alert-Id, X-Deepwick-Fired-At y la firma. En un reenvío manual también puede aparecer X-Deepwick-Retry: 1.

Este es un ejemplo de cuerpo; los identificadores están abreviados:

{
  "event": "alert.fired",
  "id": "3f1c…",
  "alert_id": "9c8b…",
  "symbol": "BTCUSDT",
  "exchange": "binance",
  "condition": "price_above",
  "threshold": 70000,
  "cross_threshold": null,
  "observed_value": 70123.4,
  "name": "BTC breakout",
  "fired_at": "2026-10-01T07:42:00.000Z"
}

Dos errores que debes evitar

Comparar cadenas directamente. Utiliza una comparación de tiempo constante para la firma: crypto.timingSafeEqual en Node, hmac.compare_digest en Python o un comparador equivalente en Go.

Volver a serializar el JSON antes de verificarlo. Espacios, escapes u orden de claves pueden cambiar. Calcula el HMAC sobre los bytes exactos recibidos, antes de transformar el cuerpo. Rechaza cabeceras ausentes o mal formadas.

Node.js

El ejemplo presupone una aplicación Express inicializada y un secreto configurado en el entorno. El callback verify conserva el cuerpo original:

import crypto from 'node:crypto';

function verifyDeepwick(req, secret) {
  const header = req.headers['x-deepwick-signature'];
  if (typeof header !== 'string' || !/^sha256=[0-9a-f]{64}$/i.test(header)) return false;
  const provided = header.slice('sha256='.length);

  // req.rawBody is the *exact* bytes that arrived on the wire. If you're
  // using express.json() you'll need to also mount `verify: (req, res, buf)
  // => { req.rawBody = buf }` to capture it before parsing.
  const expected = crypto
    .createHmac('sha256', secret)
    .update(req.rawBody)
    .digest('hex');

  const a = Buffer.from(provided, 'hex');
  const b = Buffer.from(expected, 'hex');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post('/deepwick', express.json({
  verify: (req, _res, buf) => { req.rawBody = buf; }
}), (req, res) => {
  const alert = JSON.parse(req.rawBody.toString('utf8'));
  if (!verifyDeepwick(req, process.env.DEEPWICK_SECRET)) {
    return res.status(401).send('bad signature');
  }
  // … handle the alert
  res.status(204).end();
});

Python

En FastAPI, lee primero los bytes del cuerpo. Configura SECRET desde tu gestor de secretos antes de utilizar el handler:

import hmac, hashlib
from fastapi import FastAPI, Request, HTTPException

app = FastAPI()

def verify_deepwick(raw_body: bytes, header: str | None, secret: str) -> bool:
    if not header or not header.startswith("sha256="):
        return False
    provided = header.removeprefix("sha256=")
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(provided, expected)

@app.post("/deepwick")
async def hook(request: Request):
    raw = await request.body()
    sig = request.headers.get("x-deepwick-signature")
    if not verify_deepwick(raw, sig, SECRET):
        raise HTTPException(status_code=401, detail="bad signature")
    alert = await request.json()
    # … handle the alert
    return {"ok": True}

Go

Este fragmento lee el cuerpo para verificarlo. Si necesitas procesar el JSON después, conserva esos mismos bytes: io.ReadAll consume el stream. Configura secret antes de registrar el handler y añade tu lógica de validación y procesamiento:

package main

import (
  "crypto/hmac"
  "crypto/sha256"
  "crypto/subtle"
  "encoding/hex"
  "io"
  "net/http"
  "strings"
)

func verifyDeepwick(r *http.Request, secret []byte) bool {
  h := r.Header.Get("X-Deepwick-Signature")
  if !strings.HasPrefix(h, "sha256=") {
    return false
  }
  provided, err := hex.DecodeString(strings.TrimPrefix(h, "sha256="))
  if err != nil {
    return false
  }
  body, err := io.ReadAll(r.Body)
  if err != nil {
    return false
  }
  mac := hmac.New(sha256.New, secret)
  mac.Write(body)
  expected := mac.Sum(nil)
  return subtle.ConstantTimeCompare(provided, expected) == 1
}

func handler(w http.ResponseWriter, r *http.Request) {
  if !verifyDeepwick(r, []byte(secret)) {
    http.Error(w, "bad signature", http.StatusUnauthorized)
    return
  }
  // … handle the alert
  w.WriteHeader(http.StatusNoContent)
}

Rotar el secreto

Si el secreto queda expuesto, rótalo. La siguiente petición requiere una sesión autenticada y un identificador de alerta propio; no pegues credenciales reales en documentos o registros:

curl -X POST https://deepwick.com/api/platform/alerts/<id>/rotate-secret \
  -H "Cookie: dw_session=…" \
  | jq -r .webhookSecret

El endpoint devuelve el nuevo secreto en claro una sola vez. Actualiza tu verificador. Deepwick empieza a firmar con el secreto nuevo inmediatamente; no hay un periodo de solapamiento del lado del emisor. Coordina el cambio para que el receptor admita temporalmente ambas claves y retire después la anterior.

Procesar un evento una sola vez

Tras verificar la firma, valida el contenido y utiliza el id del cuerpo firmado para deduplicar. La firma del cuerpo no autentica por separado la cabecera de identificador. Almacena el ID de forma persistente y evita que dos entregas concurrentes ejecuten dos veces la misma acción.

El receptor debe distinguir entre haber recibido una petición y haber procesado correctamente el evento. Diseña el almacenamiento del evento y los efectos posteriores de forma que un fallo no lo pierda ni lo procese dos veces.

Reintentos de alertas

La entrega de alertas realiza un reintento inmediato ante errores de red o respuestas 5xx. Si sigue fallando, el evento queda marcado como fallido y puedes reenviarlo desde el dashboard. El identificador del evento se mantiene entre entregas.

La verificación de firma es una parte de la integración. No aporta por sí sola protección contra repetición de mensajes ni garantiza la seguridad de un sistema que ejecute órdenes. Valida el evento y sus límites antes de asociarlo a cualquier acción.

webhookssecurityhmacverificationtutorial