Verificación de webhook

Verifica las firmas de webhook para asegurarte de que los eventos recibidos vienen desde DEUNA.

Los eventos de webhook incluyen una firma en el encabezado llamado X-Deuna-Signature.

📘

Usa la firma para verificar la autenticidad de los eventos.

Importancia de la verificación de firmas de webhook

Para proteger tu servidor de eventos de webhook no autorizados, DEUNA recomienda el uso de firmas de Código de Autenticación de Mensajes Basado en Hash (HMAC usando el algoritmo SHA256) para nuestros webhooks.

Para los webhooks que admiten habilitar firmas HMAC, cada evento incluye una firma calculada utilizando una clave HMAC secreta y el payload del webhook.

Al verificar esta firma, confirma que:

  • El webhook fue enviado por DEUNA.
  • El payload no fue modificado durante la transmisión.

Verifica la firma

Usa el raw body de la solicitud para validar la firma. Asegúrate de que tu framework no manipule el raw body.

Para verificar la firma del webhook:

  1. Recupera la firma. Extrae el encabezado X-Deuna-Signature de la solicitud entrante del webhook.
  2. Genera la firma esperada. Utiliza la misma clave HMAC secreta (private API Key) y el payload del webhook para generar la firma esperada.
  3. Compara la firma generada con la firma recibida. Si coinciden, entonces el webhook es legítimo.
  4. Verifica el tiempo transcurrido en el valor signed_at. Compara el tiempo transcurrido desde que se generó la firma hasta el momento actual y realiza la validación correspondiente.

🚧

Cualquier modificación en el raw body provocará que la validación falle.

Ejemplos

Compara ejemplos en varios lenguajes para verificar la firma HMAC SHA256.

import hmac
import hashlib
import base64
import json
from django.http import HttpResponse


def validate_hmac_sha256_signature(secret, payload, signature):
    secret_bytes = bytes(secret, 'utf-8')
    payload_bytes = bytes(payload, 'utf-8')
    h = hmac.new(secret_bytes, payload_bytes, hashlib.sha256)
    expected_signature = base64.b64encode(h.digest()).decode('utf-8')
    return hmac.compare_digest(signature, expected_signature)

# Example
secret = "<your-private-api-key>"

# Using Django
@csrf_exempt
def my_webhook_view(request):
  payload = request.body
  sig_header = request.META['X-Deuna-Signature']
  
  if not validate_hmac_sha256_signature(secret, payload, sig_header):
    print("Invalid signature")
    return HttpResponse(status=403)

  return HttpResponse(status=201)

const express = require('express');
const app = express();
const crypto = require('crypto');

const secret = "<your-private-api-key>"

function verifySignature(secret, signature, payload) {
  const hmac = crypto.createHmac('sha256', secret);
  const computedSignature = hmac.update(payload, 'utf8').digest('base64');
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(computedSignature));
}

// Match the raw body to content type application/json
app.post('/webhook', express.raw({ type: 'application/json' }), (request, response) => {
  const sig = request.headers['x-deuna-signature'];
  const event = request.body;

  try {
    const verified = verifySignature(secret, sig, event);
    console.log('Verified:', verified);
  } catch (err) {
    console.error(err);
    response.status(400).send(`Webhook Error: ${err.message}`);
  }

  response.json({ received: true });
});

app.listen(4242, () => console.log('Running on port 4242'));
}