Pubblicare con i webhook
Invia ogni articolo che pubblichi in GrowWriter direttamente al tuo sito web. Configura l'integrazione webhook passo dopo passo, con codice pronto all'uso.
Il tuo sito web magari non gira su nessuna delle piattaforme che GrowWriter collega di serie. Nessun problema. Con l'integrazione webhook, GrowWriter consegna ogni articolo direttamente al tuo sito nel momento in cui premi Pubblica. Stack personalizzato, CMS fatto in casa, sito statico, qualsiasi cosa. Se può ricevere una richiesta web, può ricevere i tuoi articoli.
Questa guida ti accompagna in tutta la configurazione. Non serve esperienza con i webhook. Alla fine, pubblicare un articolo in GrowWriter creerà o aggiornerà un post sul tuo sito web in automatico.
Che cos'è un webhook?
Un webhook è il modo più semplice che due applicazioni hanno per parlarsi: quando succede qualcosa in una, questa invia un messaggio a un indirizzo web che scegli tu.
Pensa a un servizio di consegne. Dai a GrowWriter il tuo indirizzo (un URL del tuo sito web). Quando un articolo è pronto, GrowWriter bussa a quella porta e consegna l'articolo completo come dati strutturati. Il tuo sito fa il resto: salvarlo, formattarlo, pubblicarlo a modo tuo.
Perché batte le alternative:
- Niente copia e incolla. L'articolo arriva con titolo, contenuto, immagini e campi SEO già organizzati.
- Niente controlli continui. Il tuo sito non deve chiedere "c'è qualcosa di nuovo?" ogni pochi minuti. GrowWriter chiama solo quando c'è qualcosa da consegnare.
- Niente vincoli di piattaforma. I webhook parlano HTTPS e JSON puri, che ogni stack web capisce.
Come funziona
L'intero flusso si capisce in venti secondi:
- Dai a GrowWriter un URL del tuo sito, e GrowWriter ti dà in cambio una chiave segreta.
- Quando pubblichi un articolo, GrowWriter invia una richiesta
POSTal tuo URL. Il corpo della richiesta è l'articolo completo in JSON, e la richiesta è firmata con il tuo secret così puoi essere certo che arrivi davvero da GrowWriter. - Il tuo sito verifica la firma, salva l'articolo e risponde con uno stato
di successo (un
2xxqualsiasi). Fatto.
Configurazione passo dopo passo
Connetti il webhook in GrowWriter
Apri la pagina Pubblica in GrowWriter e cerca la scheda Webhook nel gruppo Automazione. Fai clic su Connetti.
Inserisci il tuo URL dell'endpoint. È l'indirizzo del tuo sito web che
riceverà gli articoli, per esempio https://example.com/webhooks/growwriter.
Due regole: deve iniziare con https:// e deve essere raggiungibile da
internet (un indirizzo localhost non funzionerà).
Non hai ancora costruito l'endpoint? Nessun problema. GrowWriter non verifica l'URL in questa fase, quindi puoi connettere prima e scrivere il codice subito dopo.
Fai clic su Crea connessione.

Copia il tuo secret di firma
La finestra ora mostra il tuo Secret di firma. Inizia con whsec_ e
dimostra che una richiesta arriva davvero da GrowWriter.
Fai clic su Copia secret e conservalo in un posto sicuro sul tuo server, di solito come variabile d'ambiente:
GROWWRITER_WEBHOOK_SECRET=whsec_il_tuo_secret_quiQuesto secret viene mostrato una sola volta
GrowWriter lo conserva cifrato e non può mostrarlo di nuovo. Se lo perdi, disconnetti e riconnetti il webhook per ottenerne uno nuovo. Riconnettersi crea sempre un secret nuovo, e quello vecchio smette di funzionare.

Costruisci il tuo ricevitore
Ora aggiungi l'endpoint al tuo sito web. Deve fare quattro cose: leggere il
corpo grezzo della richiesta, verificare la firma, rispondere in fretta con
uno stato 2xx e salvare l'articolo.
Vai agli esempi di codice qui sotto e copia quello che corrisponde al tuo stack. Distribuiscilo all'URL inserito nel passaggio 1.
Invia un evento di prova
Torna nella finestra di GrowWriter e fai clic su Invia evento di prova.
GrowWriter invia un piccolo ping firmato al tuo endpoint. Ha questo
aspetto:
{
"version": "2026-08-01",
"event": "ping",
"deliveryId": "5f0c9a1e-4d2b-4f6a-9c3e-8b7d6a5e4f3c",
"sentAt": "2026-09-01T09:30:00.000Z"
}Nota che non contiene nessun articolo. È così che il tuo codice distingue una prova da una consegna vera.
Quando il tuo endpoint risponde con un 2xx, la finestra mostra Evento di
prova consegnato. Fai clic su Fatto e sei connesso. Se invece vedi
Evento di prova non riuscito, controlla la
sezione risoluzione dei problemi e riprova. Una
prova fallita non rompe mai la connessione, quindi puoi riprovare tutte le
volte che vuoi.
Pubblica un articolo
Apri uno dei tuoi articoli finiti e pubblicalo. Scegli il tuo webhook come
destinazione e GrowWriter consegnerà l'articolo completo al tuo endpoint come
evento article.published.
Se ripubblichi lo stesso articolo più avanti (dopo una modifica, per
esempio), il tuo endpoint riceverà un evento article.updated con lo stesso
id dell'articolo. È quell'id che permette al tuo sito di aggiornare il post
esistente invece di crearne un duplicato.
Esempi di codice
I due esempi qui sotto fanno il lavoro completo: verificano la firma, rispondono al ping di prova, rispondono in fretta e passano l'articolo alla tua logica di salvataggio.
Una regola conta più di tutte le altre: verifica la firma sul corpo grezzo della richiesta, esattamente com'è arrivato. Se il tuo framework fa il parsing del JSON prima e tu lo ri-serializzi, i byte cambiano e la verifica della firma fallisce.
import { createHmac, timingSafeEqual } from 'node:crypto';
// Età massima in secondi di una consegna prima di rifiutarla.
// Protegge dal replay di una vecchia richiesta catturata.
const MAX_AGE_SECONDS = 5 * 60;
function verifySignature(rawBody: string, signatureHeader: string, secret: string) {
// L'header ha questa forma: t=1756718400,v1=5257a869e7...
const parts = Object.fromEntries(
signatureHeader.split(',').map((part) => part.split('='))
);
// 1. Il timestamp deve essere recente. GrowWriter firma di nuovo ogni
// nuovo tentativo, quindi una consegna fresca porta sempre un
// timestamp fresco.
const age = Math.abs(Date.now() / 1000 - Number(parts.t));
if (!(age < MAX_AGE_SECONDS)) {
return false;
}
// 2. Ricalcola la firma: HMAC-SHA256 di "timestamp.corpoGrezzo"
// con il tuo secret di firma, in esadecimale.
const expected = createHmac('sha256', secret)
.update(`${parts.t}.${rawBody}`)
.digest('hex');
// 3. Confronta in tempo costante così il controllo non è misurabile.
const provided = Buffer.from(parts.v1 ?? '', 'hex');
const wanted = Buffer.from(expected, 'hex');
return provided.length === wanted.length && timingSafeEqual(provided, wanted);
}
export async function POST(request: Request) {
// Leggi il corpo come testo grezzo PRIMA. Non usare request.json()
// prima della verifica: la firma copre esattamente questi byte.
const rawBody = await request.text();
const signature = request.headers.get('x-growwriter-signature') ?? '';
const secret = process.env.GROWWRITER_WEBHOOK_SECRET ?? '';
if (!verifySignature(rawBody, signature, secret)) {
return new Response('Invalid signature', { status: 401 });
}
const payload = JSON.parse(rawBody);
// Il pulsante "Invia evento di prova" manda un ping senza articolo.
// Rispondere 200 qui è ciò che fa riuscire la prova.
if (payload.event === 'ping') {
return new Response('pong', { status: 200 });
}
// GrowWriter ritenta le consegne fallite con lo STESSO id di consegna.
// Se hai già elaborato questo id, rispondi semplicemente OK di nuovo.
const deliveryId = request.headers.get('x-growwriter-delivery');
// if (await alreadyProcessed(deliveryId)) return new Response('OK');
const { article } = payload;
// Upsert su article.id: "article.published" significa che è nuovo,
// "article.updated" significa che hai già visto questo id.
// Salva ciò che ti serve. article.content ti dà lo stesso articolo
// come markdown, come HTML e come blocchi strutturati. Scegline uno.
await saveArticle({
externalId: article.id, // stabile tra le ripubblicazioni
title: article.title,
slug: article.slug,
html: article.content.html,
metaDescription: article.metaDescription,
publishedAt: article.dates.firstPublishedAt,
updatedAt: article.dates.modifiedAt,
});
// GrowWriter aspetta al massimo 10 secondi. Rispondi in fretta; fai il
// lavoro pesante (come scaricare le immagini) in background dopo.
return new Response('OK', { status: 200 });
}Cosa contiene il payload
Ogni consegna è un POST con Content-Type: application/json e tre header
che ti interessano:
| Header | Cosa ti dice |
|---|---|
X-GrowWriter-Event | article.published, article.updated oppure ping |
X-GrowWriter-Delivery | Id unico di questa consegna. I nuovi tentativi mantengono lo stesso id, usalo per non elaborare due volte |
X-GrowWriter-Signature | t=<timestamp>,v1=<firma> per la verifica mostrata sopra |
Ecco un esempio ridotto del corpo per un articolo vero:
{
"version": "2026-08-01",
"event": "article.published",
"deliveryId": "9d2f7c1e-6a3b-4c8d-b5e0-1f2a3b4c5d6e",
"sentAt": "2026-09-01T09:30:00.000Z",
"publishRecord": {
"firstPublishedAt": "2026-09-01T09:30:00.000Z",
"lastPublishedAt": "2026-09-01T09:30:00.000Z"
},
"article": {
"id": "cme8x2k9r0001l7042n5q8w3v",
"language": "it",
"title": "Come scegliere scarpe da running che calzano davvero bene",
"slug": "come-scegliere-scarpe-da-running",
"metaDescription": "Impara a scegliere scarpe da running adatte al tuo piede e alla tua falcata, con controlli semplici da fare in qualsiasi negozio.",
"summary": "Una guida pratica per trovare scarpe da running...",
"keywords": {
"primary": "come scegliere scarpe da running",
"secondary": ["calzata scarpe running", "guida scarpe running"]
},
"takeaways": ["La calzata batte la marca, sempre", "..."],
"coverImage": {
"url": "https://cdn.example.com/articleimages/.../cover.jpg",
"altText": "Runner che si allaccia le scarpe su una panchina del parco"
},
"images": [
{
"url": "https://cdn.example.com/articleimages/.../gait-check.jpg",
"altText": "Vista laterale di un runner a metà falcata",
"caption": "Un rapido controllo della falcata dice più di qualsiasi scheda tecnica.",
"sectionSlug": "controlla-la-tua-falcata"
}
],
"content": {
"markdown": "Trovare il paio giusto comincia da...",
"html": "<p>Trovare il paio giusto comincia da...</p>",
"blocks": [{ "type": "paragraph", "children": [{ "type": "text", "text": "..." }] }]
},
"dates": {
"createdAt": "2026-08-28T14:00:00.000Z",
"firstPublishedAt": "2026-09-01T09:30:00.000Z",
"modifiedAt": "2026-09-01T09:29:45.000Z"
},
"brand": {
"name": "Stride Lab",
"websiteUrl": "https://stridelab.example.com"
}
}
}I campi che fanno il grosso del lavoro:
| Campo | Come usarlo |
|---|---|
article.id | La tua chiave stabile. Conservala e aggiorna il post esistente quando torna lo stesso id |
article.content | L'articolo in tre formati: markdown, html pronto all'uso e blocks strutturati per renderer personalizzati. Usa quello adatto al tuo sito |
article.title | Il titolo. Non si ripete dentro il contenuto, quindi rendilo tu |
article.metaDescription | Al massimo 160 caratteri, pensato per il tuo tag <meta name="description"> |
article.dates | Collega firstPublishedAt alla tua data di pubblicazione e modifiedAt a quella di aggiornamento |
article.coverImage / article.images | La copertina più ogni immagine del testo in ordine, con testo alternativo e didascalie quando disponibili |
Buono a sapersi
- Scarica le immagini. Gli URL delle immagini sono pubblici e funzionano
al momento della consegna, ma GrowWriter non è un hosting di immagini a
lungo termine. Salva ogni immagine (copertina e immagini nel testo) nel tuo
storage, e ricorda che gli stessi URL compaiono anche dentro
content.htmlecontent.markdown, quindi riscrivili anche lì. - Le consegne si ritentano da sole. Se il tuo endpoint è giù per un
attimo, GrowWriter riprova fino a 3 volte (aspettando 1 secondo, poi 3).
Ogni tentativo viene firmato di nuovo e mantiene lo stesso id
X-GrowWriter-Delivery. - Qualsiasi
2xxconta come successo. I redirect no. Se il tuo sito risponde301o308, la consegna fallisce: punta il webhook direttamente all'URL finale. - Non esiste un evento di eliminazione. GrowWriter crea e aggiorna soltanto. Rimuovere un post dal tuo sito resta sempre una tua scelta.
- Una connessione, un secret. Disconnettere e riconnettere il webhook crea un secret nuovo e ritira quello vecchio. Aggiorna la tua variabile d'ambiente quando lo fai.
Risoluzione dei problemi
"Il tuo endpoint non ha risposto in tempo": GrowWriter aspetta 10 secondi
per tentativo. Se il tuo codice scarica immagini o chiama altri servizi prima
di rispondere, sposta quel lavoro in un processo in background e rispondi
200 appena l'articolo è salvato.
"Il tuo endpoint ha rifiutato la consegna": il tuo endpoint ha risposto
con qualcosa di diverso da un 2xx. Cause comuni: la verifica della firma è
fallita perché il corpo è stato parsato prima della verifica (usa il corpo
grezzo), è configurato il secret sbagliato (confrontalo con quello della
finestra) oppure l'URL fa redirect (usa direttamente l'indirizzo finale).
"Consegna non riuscita dopo diversi tentativi": GrowWriter non è riuscito
a raggiungere il tuo endpoint. Controlla che l'URL sia attivo, usi https://
e sia raggiungibile pubblicamente. Gli indirizzi di reti private o
localhost sono bloccati per sicurezza. Durante lo sviluppo in locale, un
servizio di tunnel può dare al tuo server un indirizzo HTTPS pubblico
temporaneo.
La prova funziona ma gli articoli veri falliscono: il ping è minuscolo; un articolo completo può pesare qualche centinaio di kilobyte. Controlla che il tuo server accetti corpi JSON di quella dimensione e che la tua logica di salvataggio gestisca il payload completo.
Ancora bloccato? Scrivici dall'app e lo risolviamo insieme.