Guía de Integración
Sigue esta guía para integrar webhooks de Aloha Pay en tu sistema.
Paso 1: Obtén tu API Key
Sección titulada «Paso 1: Obtén tu API Key»Antes de comenzar, necesitas una API Key con los permisos correctos:
payment_links:create- Para crear links de pagopayment_links:read- Para consultar estado de pagoswebhooks:read- Para listar tus webhookswebhooks:write- Para crear/editar webhooks
Contacta a tu account manager para obtener tu API Key.
Paso 2: Crea tu Endpoint de Webhook
Sección titulada «Paso 2: Crea tu Endpoint de Webhook»Tu servidor necesita un endpoint HTTP que reciba los webhooks. Este endpoint debe:
- Aceptar requests POST
- Ser accesible públicamente (con HTTPS)
- Responder rápido (< 10 segundos)
- Verificar la firma del webhook
- Responder con código 200
Route::post('/webhooks/alohapay', [WebhookController::class, 'handle']);
// app/Http/Controllers/WebhookController.phpclass WebhookController extends Controller{ private string $webhookSecret = 'whsec_tu_secret_aqui';
public function handle(Request $request) { // 1. Obtener datos del request $payload = $request->getContent(); $signature = $request->header('X-Webhook-Signature'); $timestamp = $request->header('X-Webhook-Timestamp');
// 2. Verificar la firma if (!$this->verifySignature($payload, $signature, $timestamp)) { return response('Invalid signature', 401); }
// 3. Parsear el evento $event = json_decode($payload, true);
// 4. Procesar según el tipo de evento switch ($event['event']) { case 'payment.completed': $this->handlePaymentCompleted($event['data']['object']); break;
case 'payment.failed': $this->handlePaymentFailed($event['data']['object']); break;
case 'payment_link.cancelled': $this->handlePaymentLinkCancelled($event['data']['object']); break;
case 'payment_link.expired': $this->handlePaymentLinkExpired($event['data']['object']); break; }
// 5. Responder con 200 return response('OK', 200); }
private function verifySignature($payload, $signature, $timestamp): bool { // Evitar replay attacks (5 minutos máximo) if (abs(time() - (int)$timestamp) > 300) { return false; }
$signedPayload = "{$timestamp}.{$payload}"; $expected = 'sha256=' . hash_hmac('sha256', $signedPayload, $this->webhookSecret);
return hash_equals($expected, $signature); }
private function handlePaymentCompleted(array $payment) { // Buscar la orden usando el id del pago o la descripción $order = Order::where('payment_id', $payment['id'])->first();
if (!$order) { Log::warning('Orden no encontrada para pago', $payment); return; }
// Actualizar estado de la orden $order->update([ 'status' => 'paid', 'paid_at' => $payment['completed_at'], 'payment_amount' => $payment['amount'], 'payment_currency' => $payment['currency'], ]);
// Enviar confirmación al cliente usando customer_data $customerEmail = $payment['customer_data']['email'] ?? null; if ($customerEmail) { Mail::to($customerEmail)->send(new OrderConfirmedMail($order)); }
Log::info('Pago procesado correctamente', ['order_id' => $order->id]); }
private function handlePaymentFailed(array $payment) { $order = Order::where('payment_id', $payment['id'])->first();
if ($order) { $order->update(['status' => 'payment_failed']); // Notificar al cliente que el pago falló } }
private function handlePaymentLinkCancelled(array $paymentLink) { // El payment link fue cancelado manualmente Log::info('Payment link cancelado', ['id' => $paymentLink['id']]); }
private function handlePaymentLinkExpired(array $paymentLink) { // El payment link expiró sin ser pagado $order = Order::where('payment_id', $paymentLink['id'])->first();
if ($order && $order->status === 'pending') { $order->update([ 'status' => 'expired', 'expired_at' => $paymentLink['completed_at'], ]); } }}const express = require("express");const crypto = require("crypto");const app = express();
const WEBHOOK_SECRET = "whsec_tu_secret_aqui";
// IMPORTANTE: Usar raw body para verificar la firmaapp.post( "/webhooks/alohapay", express.raw({ type: "application/json" }), async (req, res) => { const payload = req.body.toString(); const signature = req.headers["x-webhook-signature"]; const timestamp = req.headers["x-webhook-timestamp"];
// Verificar firma if (!verifySignature(payload, signature, timestamp)) { return res.status(401).send("Invalid signature"); }
const event = JSON.parse(payload);
try { switch (event.event) { case "payment.completed": await handlePaymentCompleted(event.data.object); break; case "payment.failed": await handlePaymentFailed(event.data.object); break; case "payment_link.cancelled": await handlePaymentLinkCancelled(event.data.object); break; case "payment_link.expired": await handlePaymentLinkExpired(event.data.object); break; }
res.status(200).send("OK"); } catch (error) { console.error("Error procesando webhook:", error); res.status(500).send("Error"); } });
function verifySignature(payload, signature, timestamp) { // Evitar replay attacks const currentTime = Math.floor(Date.now() / 1000); if (Math.abs(currentTime - parseInt(timestamp)) > 300) { return false; }
const signedPayload = `${timestamp}.${payload}`; const expected = "sha256=" + crypto .createHmac("sha256", WEBHOOK_SECRET) .update(signedPayload) .digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));}
async function handlePaymentCompleted(payment) { const order = await Order.findOne({ where: { paymentId: payment.id }, });
if (!order) { console.warn("Orden no encontrada para pago:", payment.id); return; }
await order.update({ status: "paid", paidAt: payment.completed_at, paymentAmount: payment.amount, paymentCurrency: payment.currency, });
// Enviar email de confirmación usando customer_data if (payment.customer_data?.email) { await sendConfirmationEmail(order, payment.customer_data.email); }}
async function handlePaymentFailed(payment) { // Manejar pago fallido}
async function handlePaymentLinkCancelled(paymentLink) { // Manejar cancelación}
async function handlePaymentLinkExpired(paymentLink) { // Manejar expiración}import hmacimport hashlibimport timeimport jsonfrom flask import Flask, request
app = Flask(__name__)
WEBHOOK_SECRET = "whsec_tu_secret_aqui"
@app.route("/webhooks/alohapay", methods=["POST"])def handle_webhook(): payload = request.get_data(as_text=True) signature = request.headers.get("X-Webhook-Signature") timestamp = request.headers.get("X-Webhook-Timestamp")
# Verificar firma if not verify_signature(payload, signature, timestamp): return "Invalid signature", 401
event = json.loads(payload)
try: event_type = event.get("event")
if event_type == "payment.completed": handle_payment_completed(event["data"]["object"]) elif event_type == "payment.failed": handle_payment_failed(event["data"]["object"]) elif event_type == "payment_link.cancelled": handle_payment_link_cancelled(event["data"]["object"]) elif event_type == "payment_link.expired": handle_payment_link_expired(event["data"]["object"])
return "OK", 200 except Exception as e: print(f"Error procesando webhook: {e}") return "Error", 500
def verify_signature(payload: str, signature: str, timestamp: str) -> bool: # Evitar replay attacks current_time = int(time.time()) if abs(current_time - int(timestamp)) > 300: return False
# Calcular firma esperada signed_payload = f"{timestamp}.{payload}" expected = "sha256=" + hmac.new( WEBHOOK_SECRET.encode(), signed_payload.encode(), hashlib.sha256 ).hexdigest()
# Comparación segura return hmac.compare_digest(expected, signature)
def handle_payment_completed(payment): order = Order.query.filter_by(payment_id=payment["id"]).first()
if not order: print(f"Orden no encontrada para pago: {payment['id']}") return
order.status = "paid" order.paid_at = payment["completed_at"] order.payment_amount = payment["amount"] order.payment_currency = payment["currency"] db.session.commit()
# Enviar email de confirmación customer_email = payment.get("customer_data", {}).get("email") if customer_email: send_confirmation_email(order, customer_email)
def handle_payment_failed(payment): # Manejar pago fallido pass
def handle_payment_link_cancelled(payment_link): # Manejar cancelación pass
def handle_payment_link_expired(payment_link): # Manejar expiración passPaso 3: Registra tu Webhook en Aloha Pay
Sección titulada «Paso 3: Registra tu Webhook en Aloha Pay»Una vez que tu endpoint esté listo y desplegado, regístralo en Aloha Pay:
curl -X POST https://api.alohapay.co/api/external/v1/webhooks \ -H "X-API-Key: tu_api_key" \ -H "Content-Type: application/json" \ -d '{ "url": "https://tu-servidor.com/webhooks/alohapay", "description": "Webhook principal de producción", "events": ["payment.completed", "payment.failed", "payment_link.expired"] }'const response = await fetch( 'https://api.alohapay.co/api/external/v1/webhooks', { method: 'POST', headers: { 'X-API-Key': 'tu_api_key', 'Content-Type': 'application/json' }, body: JSON.stringify({ url: 'https://tu-servidor.com/webhooks/alohapay', description: 'Webhook principal de producción', events: ['payment.completed', 'payment.failed', 'payment_link.expired'] }) });const data = await response.json();
// IMPORTANTE: Guarda el secret de forma seguraconsole.log('Webhook ID:', data.data.id);console.log('Secret:', data.data.secret);import requests
headers = { 'X-API-Key': 'tu_api_key', 'Content-Type': 'application/json'}
payload = { 'url': 'https://tu-servidor.com/webhooks/alohapay', 'description': 'Webhook principal de producción', 'events': ['payment.completed', 'payment.failed', 'payment_link.expired']}
response = requests.post( 'https://api.alohapay.co/api/external/v1/webhooks', headers=headers, json=payload)data = response.json()
# IMPORTANTE: Guarda el secret de forma seguraprint('Webhook ID:', data['data']['id'])print('Secret:', data['data']['secret'])<?php$ch = curl_init();
$payload = json_encode([ 'url' => 'https://tu-servidor.com/webhooks/alohapay', 'description' => 'Webhook principal de producción', 'events' => ['payment.completed', 'payment.failed', 'payment_link.expired']]);
curl_setopt_array($ch, [ CURLOPT_URL => 'https://api.alohapay.co/api/external/v1/webhooks', CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => [ 'X-API-Key: tu_api_key', 'Content-Type: application/json' ]]);
$response = curl_exec($ch);$data = json_decode($response, true);
// IMPORTANTE: Guarda el secret de forma seguraecho 'Webhook ID: ' . $data['data']['id'] . "\n";echo 'Secret: ' . $data['data']['secret'] . "\n";Respuesta
Sección titulada «Respuesta»{ "success": true, "message": "Webhook endpoint created successfully", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "url": "https://tu-servidor.com/webhooks/alohapay", "description": "Webhook principal de producción", "events": ["payment.completed", "payment.failed", "payment_link.expired"], "secret": "whsec_a1b2c3d4e5f6g7h8i9j0...", "is_active": true, "created_at": "2025-12-04T10:00:00Z" }}Paso 4: Prueba tu Integración
Sección titulada «Paso 4: Prueba tu Integración»- Crea un Payment Link de prueba
- Completa el pago en el checkout
- Verifica que tu webhook recibió la notificación
- Confirma que tu sistema procesó el pago correctamente
Próximos Pasos
Sección titulada «Próximos Pasos»- Eventos - Conoce todos los eventos disponibles
- Verificación de Firma - Aprende a verificar webhooks
- Mejores Prácticas - Recomendaciones para producción