Un webhook ricevuto due volte può pubblicare due articoli, inviare due email o scalare due volte lo stesso credito. Il problema non è teorico: OpenAI documenta che una consegna può essere ritentata e che, in casi rari, lo stesso evento può arrivare più volte. Se WordPress esegue subito l’azione richiesta senza registrare l’identità dell’evento, ogni copia diventa una nuova operazione.
Il webhook non garantisce una sola esecuzione
Un endpoint webhook conferma la ricezione con una risposta HTTP 2xx. Se non risponde entro pochi secondi o restituisce un errore, OpenAI ritenta la consegna con backoff esponenziale fino a 72 ore; inoltre i redirect 3xx sono trattati come errori. La stessa documentazione indica l’header webhook-id come chiave di idempotenza per riconoscere gli eventi duplicati. Questo cambia il modo in cui va progettata un’integrazione WordPress: la richiesta non è un comando da eseguire alla cieca, ma un messaggio che potrebbe essere già stato elaborato. Vale per una bozza generata con l’AI, per l’aggiornamento di un ordine WooCommerce e per una notifica inviata al cliente. Il controllo deve avvenire prima di qualsiasi effetto irreversibile, non dopo aver creato il post o spedito l’email.
Registra l’evento prima di avviare il lavoro
La soluzione minima è costruire una chiave corta dall’ID del webhook e provare a salvarla con add_option(). WordPress non aggiorna un’opzione già esistente e restituisce false quando non riesce ad aggiungerla: la prima richiesta acquisisce quindi il diritto di procedere, le copie successive escono senza ripetere il lavoro. Imposta autoload a false, perché queste chiavi servono solo all’endpoint e non devono essere caricate su ogni pagina del sito.
$event_id = sanitize_text_field( $request->get_header( 'webhook-id' ) );
$key = 'lr_hook_' . hash( 'sha256', $event_id );
if ( ! add_option( $key, time(), '', false ) ) {
return new WP_REST_Response( array( 'duplicate' => true ), 200 );
}
// Accoda qui il lavoro e rispondi subito al webhook.
return new WP_REST_Response( array( 'accepted' => true ), 202 );
Prima del blocco va verificata anche la firma del webhook con il secret fornito dal servizio. L’idempotenza impedisce una doppia esecuzione; la firma impedisce che un estraneo inventi una richiesta valida solo nella forma.
I passaggi essenziali nell’endpoint WordPress
Registra la route su rest_api_init e dichiara una permission_callback, come richiede la REST API di WordPress. Nel callback leggi il corpo grezzo e gli header, verifica la firma prima di fidarti del payload, controlla che webhook-id sia presente e applica il blocco con add_option(). Solo la richiesta che crea la chiave può accodare l’operazione pesante. La risposta al provider deve partire subito; generazione AI, upload media e pubblicazione vanno eseguiti da un job asincrono, così un’elaborazione lenta non provoca nuovi tentativi. Registra nel log ID evento, tipo, ora, esito e ID del job, senza salvare secret o contenuti sensibili. Infine aggiungi una pulizia pianificata delle chiavi più vecchie del periodo di retry: un blocco permanente impedirebbe un duplicato, ma farebbe crescere inutilmente la tabella wp_options.
L’errore da evitare e la scelta operativa
Non usare un transient come unica memoria delle operazioni già eseguite. La documentazione WordPress chiarisce che la scadenza di un transient è un limite massimo, non una durata minima: una cache esterna o un’operazione sul database può farlo sparire prima. È perfetto per dati rigenerabili, non per decidere se addebitare, pubblicare o inviare una comunicazione. Per un sito con pochi eventi, add_option() con pulizia programmata è una soluzione pragmatica. Per volumi maggiori conviene una tabella dedicata con indice univoco sull’ID evento e stati come received, processing e done. La mia regola è semplice: prima rendi idempotente l’ingresso, poi automatizza l’uscita. Un workflow AI veloce ma capace di duplicare azioni non è automazione; è debito operativo eseguito più in fretta. Approfondisci anche il contratto di output prima del publish, perché deduplicazione e validazione risolvono due rischi diversi dello stesso flusso.
Fonti: OpenAI, guida ufficiale ai webhooks; WordPress, riferimento di add_option(); WordPress, Transients API; WordPress, route ed endpoint REST.

