90 lines
2.9 KiB
PHP
90 lines
2.9 KiB
PHP
<?php
|
|
|
|
namespace App\Jobs;
|
|
|
|
use App\Models\WebhookDelivery;
|
|
use App\Support\WebhookEndpoints;
|
|
use Illuminate\Bus\Queueable;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Foundation\Bus\Dispatchable;
|
|
use Illuminate\Queue\InteractsWithQueue;
|
|
use Illuminate\Queue\SerializesModels;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Str;
|
|
|
|
class SendWebhookJob implements ShouldQueue
|
|
{
|
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
|
|
public int $tries = 3;
|
|
|
|
/**
|
|
* Backoff creciente entre reintentos (segundos) — le da tiempo al
|
|
* endpoint del cliente a recuperarse si está caído momentáneamente.
|
|
*/
|
|
public array $backoff = [10, 60, 300];
|
|
|
|
public function __construct(public WebhookDelivery $delivery)
|
|
{
|
|
}
|
|
|
|
public function handle(): void
|
|
{
|
|
$endpoint = $this->delivery->endpoint;
|
|
|
|
// El endpoint pudo haberse borrado o desactivado entre que se
|
|
// encoló el job y que se procesó — no tiene sentido reintentar.
|
|
if (! $endpoint || ! $endpoint->active) {
|
|
return;
|
|
}
|
|
|
|
// Re-chequeo anti-SSRF: la URL ya se validó al crear/editar el
|
|
// webhook, pero un dominio público en ese momento puede resolver a
|
|
// una IP interna más tarde (DNS rebinding). No reintentamos esto —
|
|
// si sigue sin ser seguro en el próximo intento, va a fallar igual.
|
|
if (! WebhookEndpoints::isSafeUrl($endpoint->url)) {
|
|
$this->delivery->update([
|
|
'response_body' => 'Bloqueado: la URL resuelve a una dirección privada/reservada.',
|
|
]);
|
|
|
|
return;
|
|
}
|
|
|
|
$body = json_encode([
|
|
'event' => $this->delivery->event,
|
|
'data' => $this->delivery->payload,
|
|
]);
|
|
|
|
$signature = hash_hmac('sha256', $body, $endpoint->secret);
|
|
|
|
$this->delivery->increment('attempts');
|
|
|
|
try {
|
|
$response = Http::timeout(5)
|
|
->withHeaders([
|
|
'Content-Type' => 'application/json',
|
|
'X-Webhook-Event' => $this->delivery->event,
|
|
'X-Webhook-Signature' => 'sha256='.$signature,
|
|
])
|
|
->withBody($body, 'application/json')
|
|
->post($endpoint->url);
|
|
|
|
$this->delivery->update([
|
|
'response_status' => $response->status(),
|
|
'response_body' => Str::limit($response->body(), 1000),
|
|
'delivered_at' => $response->successful() ? now() : null,
|
|
]);
|
|
|
|
if (! $response->successful()) {
|
|
throw new \RuntimeException("El endpoint del webhook respondió {$response->status()}.");
|
|
}
|
|
} catch (\Throwable $e) {
|
|
$this->delivery->update([
|
|
'response_body' => Str::limit($e->getMessage(), 1000),
|
|
]);
|
|
|
|
throw $e;
|
|
}
|
|
}
|
|
}
|