Molte applicazioni desktop o gestionali, anche quelle scritte in Delphi o in altri linguaggi, hanno bisogno di comunicare eventi verso il web: un ordine completato, un avviso, un dato da sincronizzare. Il pattern più comune è il webhook: il sistema chiamante invia una richiesta HTTP POST con un payload JSON verso uno script PHP, che deve validarla, interpretarla e salvarla. Vediamo come costruire un endpoint di questo tipo in modo solido, dall'autenticazione fino al salvataggio su database.
Il problema: fidarsi di chi ci scrive
Un endpoint di webhook è per definizione pubblico: chiunque conosca l'URL può provare a inviargli dati. Per questo motivo, prima ancora di leggere il corpo della richiesta, va verificato che il chiamante sia autorizzato. Una soluzione robusta e diffusa è l'uso di un token JWT (JSON Web Token), generato dal sistema chiamante con una chiave segreta condivisa e verificato lato PHP con la libreria firebase/php-jwt, installabile via Composer.
Validare il token prima di toccare il database
Il token viaggia tipicamente nell'header Authorization, nella forma Bearer <token>. Se manca o non è valido, la richiesta va rifiutata subito con un HTTP 401, senza eseguire alcuna query.
Leggere il payload e salvarlo con query parametrizzate
Una volta superata la verifica, il corpo della richiesta si legge con file_get_contents('php://input') e si decodifica con json_decode. È buona norma usare il flag JSON_THROW_ON_ERROR per intercettare subito un payload malformato invece di lavorare con un valore null silenzioso. Il salvataggio su database, come sempre, deve passare da query preparate: mai concatenare i valori ricevuti direttamente in una stringa SQL.
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
// La stessa chiave segreta deve essere condivisa con il sistema che genera il token
const JWT_SECRET = 'INSERISCI-LA-TUA-CHIAVE-SEGRETA';
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
$token = str_replace('Bearer ', '', $authHeader);
if ($token === '') {
http_response_code(401);
echo json_encode(['error' => 'Token mancante']);
exit;
}
try {
JWT::decode($token, new Key(JWT_SECRET, 'HS256'));
$rawBody = file_get_contents('php://input');
$payload = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);
$pdo = new PDO(
sprintf('mysql:host=%s;dbname=%s;charset=utf8mb4', $_ENV['DB_HOST'], $_ENV['DB_NAME']),
$_ENV['DB_USER'],
$_ENV['DB_PASS'],
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
$stmt = $pdo->prepare(
'INSERT INTO webhook_data (event_id, description, value) VALUES (:event_id, :description, :value)'
);
foreach ($payload as $item) {
$stmt->execute([
':event_id' => $item['event_id'] ?? uniqid('evt_', true),
':description' => $item['description'] ?? '',
':value' => $item['value'] ?? '',
]);
}
http_response_code(200);
echo json_encode(['status' => 'success', 'message' => 'Dati ricevuti e salvati']);
} catch (Throwable $e) {
error_log('Webhook error: ' . $e->getMessage());
http_response_code(400);
echo json_encode(['status' => 'error', 'message' => 'Richiesta non valida']);
}
Rispondere in modo coerente e pensare all'idempotenza
Conviene avvolgere l'intera logica in un blocco try/catch che intercetti qualsiasi eccezione — token non valido, JSON malformato, errore SQL — e restituisca sempre una risposta JSON con lo stesso formato, variando solo il codice HTTP e il messaggio. In produzione è preferibile registrare il dettaglio dell'errore con error_log() e restituire al chiamante un messaggio generico, per non esporre informazioni sulla struttura interna del sistema. Un ultimo accorgimento utile, soprattutto se il sistema chiamante può reinviare lo stesso evento più volte in caso di timeout, è associare a ogni evento un identificativo univoco: in questo modo un doppio invio non genera righe duplicate nella tabella di destinazione.