Mejores Prácticas
Sigue estas recomendaciones para implementar webhooks de forma robusta y confiable.
1. Responde Rápido (< 10 segundos)
Sección titulada «1. Responde Rápido (< 10 segundos)»Tu endpoint debe responder en menos de 10 segundos. Si tu procesamiento es pesado, guarda el evento y procésalo en background:
// BIEN: Responder rápido, procesar despuéspublic function handle(Request $request){ // Guardar para procesar en background WebhookEvent::create([ 'event_id' => $request->input('id'), 'payload' => $request->all() ]);
return response('OK', 200); // Responder inmediatamente}
// En un job de backgroundProcessWebhookJob::dispatch($webhookEvent);app.post('/webhooks/alohapay', async (req, res) => { // Guardar para procesar después await WebhookEvent.create({ eventId: req.body.id, payload: req.body });
// Responder inmediatamente res.status(200).send('OK');
// Procesar en background (o usar una cola) processWebhookInBackground(req.body);});2. Implementa Idempotencia
Sección titulada «2. Implementa Idempotencia»Puedes recibir el mismo evento más de una vez. Usa el campo id para detectar duplicados:
public function handle(Request $request){ $eventId = $request->input('id');
// ¿Ya procesamos este evento? if (ProcessedEvent::where('event_id', $eventId)->exists()) { return response('Already processed', 200); }
// Procesar y registrar en una transacción DB::transaction(function () use ($request, $eventId) { ProcessedEvent::create(['event_id' => $eventId]); $this->processEvent($request->all()); });
return response('OK', 200);}app.post('/webhooks/alohapay', async (req, res) => { const eventId = req.body.id;
// ¿Ya procesamos este evento? const exists = await ProcessedEvent.findOne({ eventId }); if (exists) { return res.status(200).send('Already processed'); }
// Procesar y registrar await ProcessedEvent.create({ eventId }); await processEvent(req.body);
res.status(200).send('OK');});3. Usa la Descripción para Identificar
Sección titulada «3. Usa la Descripción para Identificar»Al crear payment links, usa la descripción para incluir información útil:
curl -X POST https://api.alohapay.co/api/external/v1/payment-links \ -H "X-API-Key: tu_api_key" \ -d '{ "amount": 100.00, "currency": "USD", "description": "Orden #1234 - Cliente: cus_789" }'La descripción te llega de vuelta en el webhook, facilitando la reconciliación.
4. Maneja Todos los Eventos
Sección titulada «4. Maneja Todos los Eventos»Si te suscribes a múltiples eventos, asegúrate de manejarlos todos:
switch ($event['event']) { case 'payment.completed': // Marcar orden como pagada break; case 'payment.failed': // Notificar al cliente break; case 'payment_link.expired': // Liberar inventario reservado break; default: // Evento desconocido - ignorar pero responder OK Log::info('Evento no manejado: ' . $event['event']); break;}5. Registra Todo
Sección titulada «5. Registra Todo»Guarda logs de todos los webhooks para debugging:
Log::info('Webhook recibido', [ 'event_id' => $event['id'], 'event_type' => $event['event'], 'object_id' => $event['data']['object']['id'] ?? null, 'amount' => $event['data']['object']['amount'] ?? null, 'status' => $event['data']['object']['status'] ?? null,]);Sistema de Reintentos
Sección titulada «Sistema de Reintentos»Si tu servidor no responde con código 2xx, reintentamos automáticamente:
| Intento | Espera | Tiempo acumulado |
|---|---|---|
| 1 | Inmediato | 0 |
| 2 | 1 minuto | 1 minuto |
| 3 | 5 minutos | 6 minutos |
| 4 | 15 minutos | 21 minutos |
| 5 | 1 hora | 1 hora 21 min |
| 6 | 3 horas | 4 horas 21 min |
Después del intento 6, el webhook se marca como fallido permanentemente.
Códigos HTTP y Reintentos
Sección titulada «Códigos HTTP y Reintentos»| Código | ¿Reintenta? | Razón |
|---|---|---|
| 200-299 | No | Éxito |
| 408 | Sí | Timeout |
| 429 | Sí | Rate limit |
| 500-599 | Sí | Error del servidor |
| 400-499 (otros) | No | Error del cliente |
Casos de Uso Comunes
Sección titulada «Casos de Uso Comunes»E-commerce: Confirmar Orden
Sección titulada «E-commerce: Confirmar Orden»case 'payment.completed': $order = Order::where('payment_id', $payment['id'])->first();
$order->update([ 'status' => 'paid', 'paid_at' => $payment['completed_at'] ]);
// Reducir inventario foreach ($order->items as $item) { $item->product->decrement('stock', $item->quantity); }
// Notificar al cliente usando customer_data $customerEmail = $payment['customer_data']['email'] ?? null; if ($customerEmail) { Mail::to($customerEmail)->send(new OrderConfirmedMail($order)); }
// Notificar al vendedor Notification::send($order->seller, new NewSaleNotification($order)); break;SaaS: Activar Suscripción
Sección titulada «SaaS: Activar Suscripción»case 'payment.completed': $subscription = Subscription::where('payment_id', $payment['id'])->first();
$subscription->update([ 'status' => 'active', 'started_at' => now(), 'expires_at' => now()->addMonth() ]);
// Habilitar acceso al usuario $subscription->user->update(['plan' => $subscription->plan]); break;Marketplace: Liberar Pago al Vendedor
Sección titulada «Marketplace: Liberar Pago al Vendedor»case 'payment.completed': $sale = Sale::where('payment_id', $payment['id'])->first();
// Calcular comisión usando el monto del webhook $commission = $payment['amount'] * 0.10; // 10% $sellerAmount = $payment['amount'] - $commission;
// Registrar para payout SellerPayout::create([ 'seller_id' => $sale->seller_id, 'amount' => $sellerAmount, 'currency' => $payment['currency'], 'status' => 'pending' ]); break;Troubleshooting
Sección titulada «Troubleshooting»No recibo webhooks
Sección titulada «No recibo webhooks»-
Verifica que tu endpoint sea público
Ventana de terminal curl -X POST https://tu-servidor.com/webhooks/alohapay \-H "Content-Type: application/json" \-d '{"test": true}' -
Verifica que el webhook esté activo
Ventana de terminal curl https://api.alohapay.co/api/external/v1/webhooks \-H "X-API-Key: tu_api_key" -
Revisa los logs de tu servidor
-
Verifica que no hay firewall bloqueando
Firma inválida
Sección titulada «Firma inválida»- Usa el body raw, no parseado
- Verifica el orden:
{timestamp}.{body} - Usa el secret correcto (el que recibiste al crear)
- Usa comparación segura (
hash_equals, no===)
Webhooks llegan tarde
Sección titulada «Webhooks llegan tarde»- Los webhooks tienen un delay de 1-5 segundos normalmente
- Si hay reintentos, pueden llegar horas después
- Usa el campo
createdpara saber cuándo ocurrió el evento real
Límites
Sección titulada «Límites»| Recurso | Límite |
|---|---|
| Webhooks por API Key | 10 |
| Timeout de respuesta | 10 segundos |
| Reintentos máximos | 6 |
| Tamaño máximo de payload | 64 KB |
| Antigüedad máxima de timestamp | 5 minutos |
Soporte
Sección titulada «Soporte»¿Necesitas ayuda con tu integración?
- Email: soporte@alohapay.co
- Documentación: https://developers.alohapay.co
- Status: https://status.alohapay.co