PHP

Ricevere webhook in PHP: token JWT e salvataggio sicuro su database

calendar_today personTeam EGSOFT schedule4 min di lettura
info

Disclaimer: le informazioni pubblicate in questa sezione hanno finalità di pura divulgazione tecnica. EG Software S.r.l. non si assume alcuna responsabilità per un utilizzo improprio dei contenuti, né per eventuali danni diretti o indiretti derivanti dalla loro applicazione, e non garantisce l'aggiornamento, l'accuratezza o la completezza delle informazioni riportate. Prima di utilizzare in produzione codice o procedure qui descritte, verificane sempre l'adeguatezza al proprio contesto.

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.

Altri articoli su PHP grid_viewTutte le tematiche
Scrivici su WhatsApp