Appearance
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-webhookValidació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.