7fb7f75050
Build Docker Image / build-backend (push) Successful in 18s
Co-authored-by: ISMAIL MASSERAN <topaz@Mac.dlinkrouter.local> Co-authored-by: ISMAIL MASSERAN <topaz@ISMAILs-Macbook.local> Reviewed-on: #16
123 lines
3.8 KiB
PHP
123 lines
3.8 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
/**
|
|
* Meta WhatsApp Cloud webhook: verify challenge + acknowledge events.
|
|
* Sufficient for production onboarding; delivery statuses are logged only.
|
|
*/
|
|
class WhatsappWebhookController extends Controller
|
|
{
|
|
/**
|
|
* GET /api/whatsapp/webhook
|
|
* Meta sends hub.mode, hub.verify_token, hub.challenge.
|
|
*/
|
|
public function verify(Request $request)
|
|
{
|
|
$mode = $request->query('hub_mode', $request->query('hub.mode'));
|
|
$token = $request->query('hub_verify_token', $request->query('hub.verify_token'));
|
|
$challenge = $request->query('hub_challenge', $request->query('hub.challenge'));
|
|
|
|
$expected = (string) config('whatsapp.verify_token');
|
|
|
|
if ($mode === 'subscribe' && $expected !== '' && hash_equals($expected, (string) $token)) {
|
|
Log::info('WhatsApp webhook verified');
|
|
return response((string) $challenge, 200)
|
|
->header('Content-Type', 'text/plain');
|
|
}
|
|
|
|
Log::warning('WhatsApp webhook verify failed', [
|
|
'mode' => $mode,
|
|
'token_match' => $expected !== '' && hash_equals($expected, (string) $token),
|
|
]);
|
|
|
|
return response('Forbidden', 403);
|
|
}
|
|
|
|
/**
|
|
* POST /api/whatsapp/webhook
|
|
* Acknowledge quickly; optionally check X-Hub-Signature-256 when app secret is set.
|
|
*/
|
|
public function handle(Request $request)
|
|
{
|
|
$raw = $request->getContent();
|
|
|
|
if (!$this->signatureOk($request, $raw)) {
|
|
Log::warning('WhatsApp webhook invalid signature');
|
|
return response('Invalid signature', 403);
|
|
}
|
|
|
|
$payload = json_decode($raw, true);
|
|
if (!is_array($payload)) {
|
|
return response('OK', 200);
|
|
}
|
|
|
|
try {
|
|
$this->logEvents($payload);
|
|
} catch (\Throwable $e) {
|
|
Log::error('WhatsApp webhook log failed: ' . $e->getMessage());
|
|
}
|
|
|
|
return response('OK', 200);
|
|
}
|
|
|
|
protected function signatureOk(Request $request, $raw)
|
|
{
|
|
$secret = trim((string) config('whatsapp.app_secret'));
|
|
if ($secret === '') {
|
|
return true;
|
|
}
|
|
|
|
$header = (string) $request->header('X-Hub-Signature-256', '');
|
|
if ($header === '' || strpos($header, 'sha256=') !== 0) {
|
|
return false;
|
|
}
|
|
|
|
$expected = 'sha256=' . hash_hmac('sha256', $raw, $secret);
|
|
|
|
return hash_equals($expected, $header);
|
|
}
|
|
|
|
protected function logEvents(array $payload)
|
|
{
|
|
$entries = $payload['entry'] ?? [];
|
|
if (!is_array($entries)) {
|
|
return;
|
|
}
|
|
|
|
foreach ($entries as $entry) {
|
|
$changes = $entry['changes'] ?? [];
|
|
if (!is_array($changes)) {
|
|
continue;
|
|
}
|
|
|
|
foreach ($changes as $change) {
|
|
$value = $change['value'] ?? [];
|
|
if (!is_array($value)) {
|
|
continue;
|
|
}
|
|
|
|
foreach ($value['statuses'] ?? [] as $status) {
|
|
Log::info('WhatsApp status', [
|
|
'id' => $status['id'] ?? null,
|
|
'status' => $status['status'] ?? null,
|
|
'recipient' => $status['recipient_id'] ?? null,
|
|
'errors' => $status['errors'] ?? null,
|
|
]);
|
|
}
|
|
|
|
foreach ($value['messages'] ?? [] as $message) {
|
|
Log::info('WhatsApp inbound', [
|
|
'from' => $message['from'] ?? null,
|
|
'type' => $message['type'] ?? null,
|
|
'id' => $message['id'] ?? null,
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|