Recetas
Walk-throughs cortos y enfocados que resuelven un problema concreto.
Walk-throughs cortos y enfocados que resuelven un problema concreto de integración, copiables directo.
Verificar un webhook en 5 lenguajes
Cada webhook de SkipPay viene firmado con HMAC-SHA256 sobre el body crudo. El contrato (headers, fórmula) está en Referencia → Verificación de firma; acá tienes la implementación lista para copiar en cada lenguaje.
Regla de oro en todos: lee los bytes crudos del body antes de parsear JSON — re-serializar cambia el whitespace y rompe la firma.
Node.js
import crypto from "node:crypto";
function verifyWebhook(rawBody, signatureHeader, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody) // Buffer o string — NO objeto parseado
.digest("hex");
const received = signatureHeader.replace("sha256=", "");
return crypto.timingSafeEqual(
Buffer.from(expected, "utf8"),
Buffer.from(received, "utf8"),
);
}En Express, mantén el cuerpo crudo:
app.post(
"/webhooks/skippay",
express.raw({ type: "application/json" }),
(req, res) => {
const sig = req.headers["x-gokeipay-signature"];
if (!verifyWebhook(req.body, sig, process.env.SKIPPAY_WEBHOOK_SECRET)) {
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(req.body);
// procesa el evento...
res.status(200).json({ received: true });
},
);Python
import hmac
import hashlib
def verify_webhook(raw_body: bytes, signature_header: str, secret: str) -> bool:
expected = hmac.new(
secret.encode("utf-8"),
raw_body,
hashlib.sha256,
).hexdigest()
received = signature_header.replace("sha256=", "")
return hmac.compare_digest(expected, received)En FastAPI:
@app.post("/webhooks/skippay")
async def skippay_webhook(request: Request):
raw = await request.body()
sig = request.headers.get("x-gokeipay-signature", "")
if not verify_webhook(raw, sig, settings.SKIPPAY_WEBHOOK_SECRET):
raise HTTPException(status_code=401, detail="Invalid signature")
event = json.loads(raw)
# procesa el evento...
return {"received": True}PHP
function verify_webhook(string $rawBody, string $signatureHeader, string $secret): bool {
$expected = hash_hmac("sha256", $rawBody, $secret);
$received = str_replace("sha256=", "", $signatureHeader);
return hash_equals($expected, $received);
}Go
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strings"
)
func verifyWebhook(rawBody []byte, signatureHeader, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(rawBody)
expected := hex.EncodeToString(mac.Sum(nil))
received := strings.TrimPrefix(signatureHeader, "sha256=")
return hmac.Equal([]byte(expected), []byte(received))
}Errores comunes
- Parsear JSON antes de firmar. La firma es sobre los bytes crudos.
- Usar comparación
==. UsatimingSafeEqual/hash_equals/hmac.compare_digestpara evitar timing attacks. - Guardar el secreto en cliente. Es server-only.
Recetas pendientes
Estas recetas están en cola de escritura. Mientras tanto, las páginas de producto cubren el mismo terreno:
- Embeber el widget Spot en 5 minutos — ver Spot Widget.
- Cobrar una orden AAPD desde React — ver AAPD Widget.