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.
Integración completa, de punta a punta
¿Recién partiendo? La receta Integrar Skip paso a paso (Spot & AAPD) te lleva de cero a producción con un checklist por fase.
Guía de boletas y documentos de rendición
¿Qué es el folio, qué hace rendible una boleta, cuándo se necesita orden médica o comprobante de atención? La Guía de boletas traduce los documentos chilenos al lenguaje de Skip.
Proyecto de ejemplo (open source)
Una aplicación de referencia que simula el checkout de un prestador con los flujos Spot y AAPD completos: demo desplegada + código fuente para copiar la arquitectura (credenciales, embed del widget, eventos, envío de gastos). Ver Proyecto de ejemplo — disponible para prestadores que ya tienen una public_key configurada (ingrésala arriba a la derecha).
Recetas pendientes
Estas recetas están en cola de escritura. Mientras tanto, otras páginas cubren el mismo terreno:
- Embeber el widget Spot en 5 minutos — ver Spot Widget.
- Cobrar una orden AAPD desde React — ver AAPD Widget.