Recetas

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 ==. Usa timingSafeEqual / hash_equals / hmac.compare_digest para 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.

On this page