Skip to content

Ejemplos de Implementación

Aquí encontrarás ejemplos completos de implementación del endpoint de webhooks en diferentes lenguajes de programación.

Node.js (Express)

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

// Middleware para parsear JSON
app.use(express.json());

// Configuración
const AUTH_TOKEN = process.env.QURALO_AUTH_TOKEN;
const WEBHOOK_SECRET = process.env.QURALO_WEBHOOK_SECRET;
const SUPPORTED_EVENTS = ['medication_request.created', 'encounter.created'];

// Función para validar firma HMAC
function validateSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');
  
  return signature === expectedSignature;
}

// Endpoint principal
app.post('/webhook', (req, res) => {
  try {
    // 1. Validar autenticación
    const authHeader = req.headers.authorization;
    if (!authHeader || authHeader.replace('Bearer ', '') !== AUTH_TOKEN) {
      return res.status(401).json({
        error: 'Unauthorized',
        message: 'Invalid authentication token'
      });
    }

    // 2. Validar firma
    const signature = req.headers['x-webhook-signature'];
    if (!signature || !validateSignature(req.body, signature, WEBHOOK_SECRET)) {
      return res.status(401).json({
        error: 'Unauthorized',
        message: 'Invalid webhook signature'
      });
    }

    // 3. Validar tipo de evento
    const eventType = req.headers['x-webhook-event'];
    if (!SUPPORTED_EVENTS.includes(eventType)) {
      return res.status(400).json({
        error: 'Bad Request',
        message: `Unsupported event type: ${eventType}`,
        supported_events: SUPPORTED_EVENTS
      });
    }

    // 4. Procesar evento
    const payload = req.body;
    console.log(`Processing ${eventType} event:`, payload);

    switch (eventType) {
      case 'medication_request.created':
        processMedicationRequest(payload.data);
        break;
      case 'encounter.created':
        processEncounter(payload.data);
        break;
    }

    res.status(200).json({
      status: 'success',
      message: 'Webhook processed successfully'
    });

  } catch (error) {
    console.error('Webhook error:', error);
    res.status(500).json({
      error: 'Internal Server Error',
      message: error.message
    });
  }
});

function processMedicationRequest(data) {
  // Implementar lógica para procesar receta médica
  console.log('Processing medication request:', data.prescription_id);
  // Aquí integrarías con tu sistema HIS
}

function processEncounter(data) {
  // Implementar lógica para procesar consulta médica
  console.log('Processing encounter:', data.encounter_id);
  // Aquí integrarías con tu sistema HIS
}

app.listen(3000, () => {
  console.log('Webhook server running on port 3000');
});

PHP

php
<?php
header('Content-Type: application/json');

// Configuración
$auth_token = $_ENV['QURALO_AUTH_TOKEN'] ?? '';
$webhook_secret = $_ENV['QURALO_WEBHOOK_SECRET'] ?? '';
$supported_events = ['medication_request.created', 'encounter.created'];

// Función para validar firma HMAC
function validateSignature($payload, $signature, $secret) {
    $expected_signature = hash_hmac('sha256', $payload, $secret);
    return hash_equals($expected_signature, $signature);
}

// Función para enviar respuesta JSON
function sendResponse($status, $data) {
    http_response_code($status);
    echo json_encode($data);
    exit;
}

try {
    // Verificar método HTTP
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        sendResponse(405, [
            'error' => 'Method Not Allowed',
            'message' => 'Only POST method is allowed'
        ]);
    }

    // 1. Validar autenticación
    $auth_header = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
    if (!$auth_header || !str_starts_with($auth_header, 'Bearer ')) {
        sendResponse(401, [
            'error' => 'Unauthorized',
            'message' => 'Missing or invalid Authorization header'
        ]);
    }

    $token = str_replace('Bearer ', '', $auth_header);
    if ($token !== $auth_token) {
        sendResponse(401, [
            'error' => 'Unauthorized',
            'message' => 'Invalid authentication token'
        ]);
    }

    // 2. Validar firma
    $raw_payload = file_get_contents('php://input');
    $signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
    
    if (!$signature || !validateSignature($raw_payload, $signature, $webhook_secret)) {
        sendResponse(401, [
            'error' => 'Unauthorized',
            'message' => 'Invalid webhook signature'
        ]);
    }

    // 3. Validar tipo de evento
    $event_type = $_SERVER['HTTP_X_WEBHOOK_EVENT'] ?? '';
    if (!in_array($event_type, $supported_events)) {
        sendResponse(400, [
            'error' => 'Bad Request',
            'message' => "Unsupported event type: $event_type",
            'supported_events' => $supported_events
        ]);
    }

    // 4. Procesar payload
    $payload = json_decode($raw_payload, true);
    if (json_last_error() !== JSON_ERROR_NONE) {
        sendResponse(400, [
            'error' => 'Bad Request',
            'message' => 'Invalid JSON payload'
        ]);
    }

    // 5. Procesar evento
    error_log("Processing $event_type event: " . json_encode($payload));

    switch ($event_type) {
        case 'medication_request.created':
            processMedicationRequest($payload['data']);
            break;
        case 'encounter.created':
            processEncounter($payload['data']);
            break;
    }

    sendResponse(200, [
        'status' => 'success',
        'message' => 'Webhook processed successfully'
    ]);

} catch (Exception $e) {
    error_log("Webhook error: " . $e->getMessage());
    sendResponse(500, [
        'error' => 'Internal Server Error',
        'message' => $e->getMessage()
    ]);
}

function processMedicationRequest($data) {
    // Implementar lógica para procesar receta médica
    error_log("Processing medication request: " . $data['prescription_id']);
    // Aquí integrarías con tu sistema HIS
}

function processEncounter($data) {
    // Implementar lógica para procesar consulta médica
    error_log("Processing encounter: " . $data['encounter_id']);
    // Aquí integrarías con tu sistema HIS
}
?>

Python (Flask)

python
import os
import json
import hmac
import hashlib
from flask import Flask, request, jsonify
import logging

app = Flask(__name__)

# Configuración
AUTH_TOKEN = os.getenv('QURALO_AUTH_TOKEN', '')
WEBHOOK_SECRET = os.getenv('QURALO_WEBHOOK_SECRET', '')
SUPPORTED_EVENTS = ['medication_request.created', 'encounter.created']

# Configurar logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def validate_signature(payload, signature, secret):
    """Validar firma HMAC SHA256"""
    expected_signature = hmac.new(
        secret.encode('utf-8'),
        payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    
    return hmac.compare_digest(expected_signature, signature)

def process_medication_request(data):
    """Procesar receta médica"""
    logger.info(f"Processing medication request: {data.get('prescription_id')}")
    # Aquí integrarías con tu sistema HIS

def process_encounter(data):
    """Procesar consulta médica"""
    logger.info(f"Processing encounter: {data.get('encounter_id')}")
    # Aquí integrarías con tu sistema HIS

@app.route('/webhook', methods=['POST'])
def webhook():
    try:
        # 1. Validar autenticación
        auth_header = request.headers.get('Authorization')
        if not auth_header or not auth_header.startswith('Bearer '):
            return jsonify({
                'error': 'Unauthorized',
                'message': 'Missing or invalid Authorization header'
            }), 401

        token = auth_header.replace('Bearer ', '')
        if token != AUTH_TOKEN:
            return jsonify({
                'error': 'Unauthorized',
                'message': 'Invalid authentication token'
            }), 401

        # 2. Validar firma
        signature = request.headers.get('X-Webhook-Signature')
        raw_payload = request.get_data(as_text=True)
        
        if not signature or not validate_signature(raw_payload, signature, WEBHOOK_SECRET):
            return jsonify({
                'error': 'Unauthorized',
                'message': 'Invalid webhook signature'
            }), 401

        # 3. Validar tipo de evento
        event_type = request.headers.get('X-Webhook-Event')
        if event_type not in SUPPORTED_EVENTS:
            return jsonify({
                'error': 'Bad Request',
                'message': f'Unsupported event type: {event_type}',
                'supported_events': SUPPORTED_EVENTS
            }), 400

        # 4. Procesar payload
        payload = request.get_json()
        if not payload:
            return jsonify({
                'error': 'Bad Request',
                'message': 'Invalid JSON payload'
            }), 400

        # 5. Procesar evento
        logger.info(f"Processing {event_type} event: {payload}")

        if event_type == 'medication_request.created':
            process_medication_request(payload.get('data', {}))
        elif event_type == 'encounter.created':
            process_encounter(payload.get('data', {}))

        return jsonify({
            'status': 'success',
            'message': 'Webhook processed successfully'
        }), 200

    except Exception as e:
        logger.error(f"Webhook error: {str(e)}")
        return jsonify({
            'error': 'Internal Server Error',
            'message': str(e)
        }), 500

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=3000, debug=False)

Java (Spring Boot)

java
@RestController
@RequestMapping("/webhook")
public class WebhookController {
    
    private static final Logger logger = LoggerFactory.getLogger(WebhookController.class);
    
    @Value("${quralo.auth.token}")
    private String authToken;
    
    @Value("${quralo.webhook.secret}")
    private String webhookSecret;
    
    private static final List<String> SUPPORTED_EVENTS = Arrays.asList(
        "medication_request.created", 
        "encounter.created"
    );
    
    @PostMapping
    public ResponseEntity<?> handleWebhook(
            @RequestHeader("Authorization") String authorization,
            @RequestHeader("X-Webhook-Event") String eventType,
            @RequestHeader("X-Webhook-Signature") String signature,
            @RequestBody String rawPayload,
            HttpServletRequest request) {
        
        try {
            // 1. Validar autenticación
            if (!authorization.startsWith("Bearer ") || 
                !authorization.substring(7).equals(authToken)) {
                return ResponseEntity.status(401).body(Map.of(
                    "error", "Unauthorized",
                    "message", "Invalid authentication token"
                ));
            }
            
            // 2. Validar firma
            if (!validateSignature(rawPayload, signature, webhookSecret)) {
                return ResponseEntity.status(401).body(Map.of(
                    "error", "Unauthorized",
                    "message", "Invalid webhook signature"
                ));
            }
            
            // 3. Validar tipo de evento
            if (!SUPPORTED_EVENTS.contains(eventType)) {
                return ResponseEntity.status(400).body(Map.of(
                    "error", "Bad Request",
                    "message", "Unsupported event type: " + eventType,
                    "supported_events", SUPPORTED_EVENTS
                ));
            }
            
            // 4. Procesar payload
            ObjectMapper mapper = new ObjectMapper();
            Map<String, Object> payload = mapper.readValue(rawPayload, Map.class);
            
            // 5. Procesar evento
            logger.info("Processing {} event: {}", eventType, payload);
            
            switch (eventType) {
                case "medication_request.created":
                    processMedicationRequest((Map<String, Object>) payload.get("data"));
                    break;
                case "encounter.created":
                    processEncounter((Map<String, Object>) payload.get("data"));
                    break;
            }
            
            return ResponseEntity.ok(Map.of(
                "status", "success",
                "message", "Webhook processed successfully"
            ));
            
        } catch (Exception e) {
            logger.error("Webhook processing error", e);
            return ResponseEntity.status(500).body(Map.of(
                "error", "Internal Server Error",
                "message", e.getMessage()
            ));
        }
    }
    
    private boolean validateSignature(String payload, String signature, String secret) {
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
            mac.init(secretKey);
            
            byte[] hash = mac.doFinal(payload.getBytes());
            String expectedSignature = DatatypeConverter.printHexBinary(hash).toLowerCase();
            
            return signature.equals(expectedSignature);
        } catch (Exception e) {
            logger.error("Error validating signature", e);
            return false;
        }
    }
    
    private void processMedicationRequest(Map<String, Object> data) {
        logger.info("Processing medication request: {}", data.get("prescription_id"));
        // Aquí integrarías con tu sistema HIS
    }
    
    private void processEncounter(Map<String, Object> data) {
        logger.info("Processing encounter: {}", data.get("encounter_id"));
        // Aquí integrarías con tu sistema HIS
    }
}

Consideraciones para Todos los Lenguajes

Variables de Entorno

Configura las siguientes variables de entorno en tu sistema:

bash
QURALO_AUTH_TOKEN=tu-token-de-autenticacion
QURALO_WEBHOOK_SECRET=tu-clave-secreta-del-webhook

Validación de Campos Requeridos

Asegúrate de validar los campos requeridos según el tipo de evento:

  • medication_request.created: prescription_id, patient_id
  • encounter.created: encounter_id, patient_id

Logs y Auditoría

Implementa logging adecuado para:

  • Eventos recibidos
  • Errores de procesamiento
  • Respuestas enviadas

Pruebas de Integración

Utiliza las herramientas de testing proporcionadas en la documentación para validar tu implementación antes de ponerla en producción.

Documentación de Quralo