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.
Ultimo aggiornamento il
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. Connetterai il webhook in GrowWriter, aggiungerai un piccolo pezzo di codice al tuo sito web e lo metterai alla prova. Alla fine, pubblicare un articolo in GrowWriter creerà o aggiornerà un post sul tuo sito web in automatico. Ti serve solo un sito web a cui puoi aggiungere codice.
Vuoi prima la strada più breve? L'avvio rapido porta un solo stack fino a un evento di prova consegnato, in una sola pagina; torna qui per salvare l'articolo. Quando ti serve ogni dettaglio, lo trovi in due pagine di accompagnamento: il riferimento del payload e la risoluzione dei problemi.
Sai già cos'è un webhook? Vai alla configurazione.
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 è fatto di tre passaggi:
- Dai a GrowWriter l'indirizzo di un endpoint del tuo sito (un URL che accetta richieste, servito da un piccolo pezzo di codice che chiamiamo ricevitore), e GrowWriter ti dà in cambio una chiave segreta.
- Quando pubblichi un articolo, GrowWriter invia una richiesta
POSTa quell'endpoint. 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), includendo se vuoi l'URL del nuovo post così GrowWriter può linkarlo. Un redirect (3xx) non è un successo. Fatto.
Perché la firma e il secret?
Il tuo endpoint è un URL pubblico. Chiunque su internet può inviargli una
richiesta POST, e un finto "articolo" mandato da uno sconosciuto avrebbe
lo stesso aspetto di uno vero. La firma è ciò che ti permette di
distinguerli. Ecco l'idea completa in quattro passaggi:
- Il secret è una password che conoscete solo tu e GrowWriter. Lo ricevi una volta sola, durante la configurazione, e lo conservi sul tuo server. Non viaggia mai dentro le richieste.
- GrowWriter timbra ogni richiesta con un'impronta. Prima dell'invio calcola un HMAC: un hash del corpo della richiesta e dell'ora corrente, mescolati con il tuo secret. Solo chi possiede il secret può produrre quell'impronta.
- Il tuo endpoint ricalcola e confronta. Esegui lo stesso calcolo sul
corpo che hai ricevuto. Se il tuo risultato corrisponde all'impronta
nell'header
X-GrowWriter-Signature, sono dimostrate due cose in un colpo solo: la richiesta arriva davvero da GrowWriter e nessuno ha modificato il corpo lungo la strada. - Il timestamp blocca i replay. L'ora di invio è incorporata nell'impronta, quindi un attaccante non può registrare una richiesta vera e rimandarla più tardi. Rifiuta tutto ciò che è più vecchio di cinque minuti e una richiesta catturata scade prima di poter essere riusata.
Non devi costruire nulla di tutto questo da solo. Ogni esempio di codice qui sotto fa tutte e quattro le parti. Questa sezione spiega solo a cosa serve quel codice.
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.
Tre regole: deve iniziare con https://, non può contenere nome utente o
password, e il suo host deve essere raggiungibile da internet (localhost e
gli indirizzi di reti private vengono rifiutati).
Non hai ancora costruito l'endpoint? Nessun problema. GrowWriter controlla l'indirizzo ma in questa fase non lo chiama, quindi puoi connettere prima e scrivere il codice subito dopo.
Usa l'URL esatto su cui risponde il tuo sito, senza redirect
Se il tuo host reindirizza example.com a www.example.com (o il
contrario), inserisci l'indirizzo verso cui reindirizza. GrowWriter non
segue mai un redirect, quindi un indirizzo che reindirizza fa fallire ogni
consegna. Non sai quale dei due usa il tuo sito? Controllalo nella
risoluzione dei problemi.
Puoi cambiare l'indirizzo più avanti con Modifica URL nella riga del
webhook, e il tuo secret resta lo stesso.
Fai clic su Crea connessione. La riga del webhook mostra Non verificato finché il primo evento di prova o la prima pubblicazione non riesce.

Copia il tuo secret di firma
La finestra ora mostra il tuo Secret di firma. Inizia con whsec_ ed è
la password condivisa di Perché la firma e il
secret?: la chiave che il tuo codice usa per controllare
che una richiesta arrivi 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_your_secret_hereUn dettaglio su cui in tanti inciampano: quando il tuo codice verifica una firma,
la chiave è la stringa intera, prefisso whsec_ incluso. Non togliere il
prefisso. E se vuoi validare il valore nella tua configurazione, un secret è
sempre whsec_ seguito da 43 caratteri base64 URL-safe.
Questo 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 endpoint
Da qui in avanti è un lavoro da sviluppatore. Manda questa pagina alla persona che gestisce il tuo sito web; le serve il secret del passaggio 2. Tu torni al passaggio 4 per inviare l'evento di prova.
L'endpoint è un piccolo pezzo di codice, il ricevitore, all'URL del
passaggio 1. Legge la richiesta esattamente com'è arrivata, controlla la
firma, risponde con uno stato 2xx e salva l'articolo. Se a quel punto
conosce l'URL pubblico del post, può restituirlo come {"url": "https://…"}
e GrowWriter mostra un link Vedi articolo nella finestra di
pubblicazione.
Non sai quale esempio usare? Se il tuo sito gira su Next.js, Vercel o Netlify, parti dalla scheda Next.js. Se hai un server tuo, scegli il linguaggio in cui è scritto. Copia il file dagli esempi di codice qui sotto, metti il secret del passaggio 2 nelle variabili d'ambiente del tuo hosting provider (impostazioni che il tuo host conserva fuori dal codice) e distribuisci, così che il file risponda all'URL del tuo endpoint.
Ogni esempio risponde già all'evento di prova. Salvare l'articolo è dove entra in gioco la tua configurazione: se il tuo sito usa un CMS o un database, Salva l'articolo nel tuo CMS ti dice cosa preparare.
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-09-06",
"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.
GrowWriter firma il ping esattamente come una consegna vera e lo invia anche
nello stesso modo: fino a tre tentativi, ognuno con 10 secondi di attesa per
una risposta 2xx qualsiasi, quindi un endpoint che non risponde mai
impiega circa mezzo minuto a fallire. Quando il tuo endpoint risponde, la
finestra mostra Evento di prova consegnato. Fai clic su Fatto e sei
connesso.

Se la prova fallisce, la finestra ne indica il motivo. L'URL del tuo
endpoint reindirizza. Inserisci l'indirizzo a cui reindirizza e riprova
significa che il tuo indirizzo manda GrowWriter da un'altra parte, e il
messaggio mostra l'indirizzo da usare al suo posto.
Il tuo endpoint ha rifiutato la consegna riporta lo stato restituito dal
tuo endpoint e i primi 200 caratteri del corpo della risposta, quindi un
404 o un 500 con un breve messaggio di errore si spiega da solo.
Consegna non riuscita dopo diversi tentativi significa che GrowWriter
non è riuscito a connettersi affatto: l'indirizzo non è pubblico, oppure il
sito è giù. Ogni messaggio ha la sua voce nella
pagina di risoluzione dei problemi.
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
canale 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. "Aggiornato" significa
semplicemente che una pubblicazione precedente su questo webhook è riuscita;
una seconda connessione webhook riceve di nuovo article.published, perché
non ha mai visto l'articolo.
Se più avanti elimini l'articolo in GrowWriter, o disconnetti il webhook,
il tuo endpoint riceve article.unpublished con solo l'id, lo slug e
la language dell'articolo. Nascondere o eliminare il post è una tua
scelta; gli esempi qui sotto lo registrano soltanto nei log.
Esempi di codice
Scegli la scheda che corrisponde al tuo stack. Ogni esempio verifica la firma (i quattro passaggi di Perché la firma e il secret?), risponde al ping di prova, salta le consegne già elaborate e passa l'articolo a una funzione di salvataggio nella forma che un CMS si aspetta. Gli esempi Next.js ed Express registrano il post nei log, perché gli host serverless danno al tuo codice un filesystem in sola lettura; gli esempi Flask, PHP, Rails e Go scrivono un file JSON per articolo. Sostituisci l'uno o l'altro con una chiamata al tuo database. Le ragioni dietro ogni riga sono in Salva l'articolo nel tuo CMS.
Due dettagli che vedrai nel codice. Gli esempi rifiutano qualsiasi consegna con un timestamp più vecchio di cinque minuti: è la finestra anti-replay della sezione qui sopra, abbastanza stretta da rendere inutili i replay e abbastanza larga da assorbire lo scarto di orologio tra server. E confrontano le firme in tempo costante: il confronto dura lo stesso tempo sia quando un tentativo è quasi giusto sia quando è completamente sbagliato, così un attaccante non può imparare nulla misurando i tempi di risposta.
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. Ogni esempio legge il corpo grezzo per primo esattamente per questo motivo.
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;
// Id di consegna già elaborati fino in fondo. Basta per un server solo;
// in produzione usa invece una tabella nel tuo database. Se il tuo host
// esegue il codice come funzioni di breve durata (serverless), questa
// lista non sopravvive tra una richiesta e l'altra.
const processedDeliveries = new Set<string>();
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
// 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
// come chiave il tuo secret di firma INTERO (prefisso whsec_ incluso).
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);
}
// Trasforma il payload nella forma che un CMS si aspetta. Ogni riga applica
// una delle sei regole di "Salva l'articolo nel tuo CMS" qui sotto.
function toPost(article: any) {
const images = [article.coverImage, ...article.images].filter(Boolean);
return {
growwriter_id: article.id, // regola 1: un campo dedicato, mai una chiave sparsa
title: article.title,
slug: article.slug,
summary: article.summary ?? '', // omesso dal payload se assente
body: article.content.markdown, // regola 6: fai l'escape se lo compili come MDX
published_at: article.dates.firstPublishedAt.slice(0, 10), // regola 2: YYYY-MM-DD
updated_at: article.dates.modifiedAt.slice(0, 10),
status: 'published', // regola 3: il payload non ha un flag di pubblicazione
seo_keyword: article.keyword ?? '', // regola 4: la parola chiave è un campo SEO, non un tag
faq: article.relatedQuestions, // regola 4: materiale per le FAQ, non tag
image_urls: images.map((image: any) => image.url), // regola 5: scarica e ridimensiona
};
}
// Conserva il post nel tuo database o CMS. Questo esempio lo registra
// soltanto nei log, di proposito: Vercel e la maggior parte degli host
// serverless montano la cartella del codice in sola lettura, quindi
// scrivere un file qui farebbe crashare la prima consegna vera con un
// 500. Qualunque cosa conservi, usa article.id come chiave: quando lo
// stesso articolo viene ripubblicato ("article.updated") sovrascrivi lo
// stesso record. Quel crea-o-aggiorna è tutto ciò che "upsert" significa.
// Restituisce l'URL pubblico del post, così l'handler può passarlo a
// GrowWriter.
async function saveArticle(article: any, event: string) {
const post = toPost(article);
const verb = event === 'article.published' ? 'Created' : 'Updated';
console.log(`${verb} ${post.growwriter_id}: ${post.title}`);
return `https://www.example.com/blog/${post.slug}`;
}
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 });
}
// Eliminare l'articolo in GrowWriter (o disconnettere il webhook) manda
// article.unpublished con solo id, slug e lingua. Nascondi o elimina qui
// il tuo post; questo esempio lo registra soltanto nei log.
if (payload.event === 'article.unpublished') {
console.log(`Unpublished ${payload.article.id}`);
return new Response('OK', { status: 200 });
}
// I nuovi tentativi riusano lo stesso id di consegna. Se questo id è
// già completato, l'articolo è salvato: rispondi di nuovo OK e salta
// il lavoro.
const deliveryId = request.headers.get('x-growwriter-delivery') ?? '';
if (processedDeliveries.has(deliveryId)) {
return new Response('OK', { status: 200 });
}
// payload.event ti dice il caso: "article.published" per un articolo
// nuovo, "article.updated" quando torna un id che hai già visto.
const postUrl = await saveArticle(payload.article, payload.event);
processedDeliveries.add(deliveryId);
// GrowWriter aspetta al massimo 10 secondi per tentativo. Una scrittura
// nel database ci sta comodamente; se il tuo salvataggio è più lento,
// vedi "E il limite dei 10 secondi?" qui sotto. Restituire l'URL del
// post è facoltativo: dà alla finestra di pubblicazione un link "Vedi
// articolo". Un semplice 'OK' va bene se l'URL non lo conosci ancora.
return Response.json({ url: postUrl }, { status: 200 });
}Usi un altro stack?
Gli esempi si traducono in qualsiasi linguaggio in grado di calcolare un HMAC. L'unico passaggio che si tende a sbagliare è il primo: verifica la firma sul corpo grezzo della richiesta, esattamente com'è arrivato. Ecco dove trovarlo in altri stack comuni:
| Stack | Corpo grezzo della richiesta |
|---|---|
| Django | request.body |
| Laravel | $request->getContent() |
| Sinatra | request.body.read |
| Cloudflare Workers | await request.text() |
| Deno / Bun | await request.text() |
E il limite dei 10 secondi?
GrowWriter aspetta la tua risposta fino a 10 secondi per tentativo. Salvare un record JSON ci sta comodamente. Se il tuo endpoint fa lavoro più lento, come scaricare ogni immagine prima di salvare, hai due opzioni:
- Appoggiati ai nuovi tentativi (consigliato per gli endpoint semplici).
Lascia l'handler esattamente com'è mostrato: crea o aggiorna in base ad
article.ide salta gli id di consegna già completati, quindi eseguirlo due volte è innocuo. Se il primo tentativo va in timeout, il tuo server continua a lavorare. Il nuovo tentativo trova il lavoro già fatto, o lo rifà, e risponde entro la finestra. Non si perde nulla, e non scrivi codice extra. - Prima rispondi, poi lavora. Salva il payload grezzo, rispondi
200subito ed elabora tutto in un job in background. Così batti sempre il cronometro, ma conosci il prezzo: una volta risposto200, la consegna conta come riuscita. Se poi il tuo job in background fallisce, GrowWriter non riproverà, e l'unico modo per ricevere di nuovo l'articolo è pubblicarlo di nuovo. E siccome il post non esiste ancora quando rispondi, non puoi restituirne l'URL, quindi la finestra di pubblicazione non mostra nessun link Vedi articolo per quella pubblicazione.
Salva l'articolo nel tuo CMS
Il payload ha la forma di GrowWriter. Il tuo CMS, il tuo database o il tuo generatore di siti statici hanno la loro forma, e quasi tutti la controllano: uno schema è la lista dei campi che un tipo di contenuto ammette e il formato che ognuno accetta. Il codice qui sopra traduce tra le due forme. Questa sezione spiega le sei regole che ci stanno dietro, così puoi adattarle alla tua configurazione.
Prima della tua prima consegna, apri il pannello di amministrazione del tuo CMS e rispondi a cinque domande:
- Dove vivrà l'id di GrowWriter?
- Che formato accettano i tuoi campi data?
- Quale campo segna un post come pubblicato, e qual è il suo valore predefinito per i contenuti nuovi?
- Dove devono andare le parole chiave, se da qualche parte?
- Dove verranno conservate le immagini?
Regola 1: dai all'id di GrowWriter un campo tutto suo
Cosa. article.id è composto da 25 lettere minuscole e cifre, e inizia
con c. È sicuro negli URL e nei nomi di file e non cambia mai.
Perché. La maggior parte dei CMS rifiuta i campi che non dichiara.
Scrivere l'id come chiave sparsa accanto al titolo rompe il post nel
pannello di amministrazione, anche se il sito continua a renderizzarlo
(l'errore segnalato aveva questo aspetto:
Key on object value "growwriterId" is not allowed).
Cosa fare. Aggiungi un campo dedicato come growwriter_id al tuo tipo
di contenuto prima della prima consegna, oppure tieni una tabella separata
che associa gli id ai tuoi post. Chiama i file con l'id, mai con lo slug.
Regola 2: converti le date
Cosa. Ogni data è in formato ISO 8601, UTC, con i millisecondi, come
2026-09-02T11:29:14.967Z.
Perché. I campi data senza orario di un CMS accettano YYYY-MM-DD e
rifiutano un timestamp completo.
Cosa fare. Prendi i primi dieci caratteri per i campi data senza
orario. Usa dates.firstPublishedAt come data di pubblicazione e
dates.modifiedAt come data di aggiornamento. modifiedAt è l'ora della
richiesta di pubblicazione, non dell'ultima modifica, quindi non
confrontare mai le due date per rilevare un aggiornamento: te lo dice
l'event.
Regola 3: imposta tu lo stato di pubblicazione
Cosa. Il payload non ha nessun flag di pubblicazione o di bozza. Il flag è l'evento, e sparisce quando la richiesta finisce.
Perché. Molti CMS trattano i contenuti nuovi come bozze per impostazione predefinita. Un post che era online perché la tua build ignorava quel valore predefinito va offline la prima volta che qualcuno lo salva nel pannello di amministrazione.
Cosa fare. Scrivi il tuo stato di pubblicazione in modo esplicito
(status: "published", draft: false, o qualunque cosa usi il tuo CMS)
sia su article.published sia su article.updated. Quando arriva
article.unpublished (l'articolo è stato eliminato in GrowWriter, oppure
il webhook è stato disconnesso), riporta indietro quello stato o elimina
il post; il payload porta solo id, slug e lingua, quindi la decisione è
tua.
Regola 4: la parola chiave e le domande non sono tag
Cosa. article.keyword è l'unico termine di ricerca per cui
l'articolo è stato scritto, così come l'ha digitato l'autore. Può mancare.
article.relatedQuestions è la lista delle domande che Google mostra come
"le persone hanno chiesto anche" per quella parola chiave, nella lingua
dell'articolo; è sempre presente e può essere vuota.
Perché. Un termine di ricerca non è un tag, e una domanda non è una categoria. Renderizzati come tag mettono frasi di ricerca sulle tue pagine pubbliche; usate come FAQ sotto l'articolo, le domande si guadagnano il loro posto.
Cosa fare. Conserva la parola chiave per i tuoi strumenti SEO. Mostra
le domande come blocco FAQ, o come dati strutturati FAQPage se il tuo
renderer li supporta. Se vuoi dei tag, decidili nel tuo CMS.
Regola 5: scarica e ottimizza le immagini
Cosa. Gli URL delle immagini puntano allo storage pubblico di GrowWriter. Oggi non scadono, ma GrowWriter non è un hosting di immagini a lungo termine. I file arrivano così come sono stati recuperati, alla risoluzione originale, e una singola immagine può superare i 10 MB.
Perché. Committare o servire originali da diversi megabyte rallenta ogni pagina.
Cosa fare. Scarica ogni immagine (copertina e immagini nel testo),
ridimensionala alla larghezza massima che serve al tuo layout, comprimila e
conservala dalla tua parte. Poi riscrivi gli URL nel formato di contenuto
che salvi. Due cose te lo rendono facile: le immagini invariate mantengono
esattamente gli stessi URL tra un aggiornamento e l'altro, quindi puoi
saltare tutto ciò che hai già copiato, e gli stessi URL compaiono dentro il
contenuto. Chiama i tuoi file a partire da article.id e dalla posizione
dell'immagine; images[].filename è opzionale e non ha estensione, e
sectionSlug dice solo per quale sezione della scaletta è stata creata
l'immagine, quindi non analizzarlo e non usarlo come chiave.
let html = article.content.html;
for (const image of [article.coverImage, ...article.images].filter(Boolean)) {
const localUrl = await mirrorImage(image.url); // scarica + salva nel tuo storage
html = html.replaceAll(image.url, localUrl);
}Regola 6: fai l'escape per il tuo renderer
Cosa. La prosa normale contiene caratteri come {, } e <.
Perché. MDX, JSX e i motori di template li leggono come codice, e la build fallisce al primo articolo che li contiene.
Cosa fare. Conserva il markdown come dati e renderizzalo con un
renderer Markdown, oppure fai l'escape di quei caratteri prima di
compilare. content.html ha già l'escape e usa un insieme fisso di tag,
quindi è la scelta sicura per l'inserimento diretto.
Gestire aggiornamenti e traduzioni
- Aggiornamenti: stesso
id, sovrascrivi il post. Modificare il titolo mantiene lo slug; solo scegliere un nuovo titolo nel writer ne produce uno nuovo. Quando cambia, aggiorna l'indirizzo del post e valuta un redirect da quello vecchio. Le immagini sostituite ricevono URL nuovi. Tratta l'ultima consegna come autorevole per ogni campo. - Traduzioni: ogni lingua è un articolo a sé con il proprio
id. GrowWriter non le collega tra loro. Abbina le lingue con i tuoi dati, usandoarticle.language(un codice di due lettere comees).
Prova in locale
Non ti serve pubblicare un articolo vero per mettere alla prova il tuo
endpoint. Questo snippet firma un ping di prova esattamente come fa
GrowWriter, usando openssl, e lo invia al tuo server locale con curl:
# Usa lo stesso secret che il tuo server locale ha in GROWWRITER_WEBHOOK_SECRET.
SECRET="whsec_your_secret_here"
BODY='{"version":"2026-09-06","event":"ping","deliveryId":"9d2f7c1e-6a3b-4c8d-b5e0-1f2a3b4c5d6e","sentAt":"2026-09-01T09:30:00.000Z"}'
# Firma "timestamp.corpo" con HMAC-SHA256, esattamente come GrowWriter.
TIMESTAMP=$(date +%s)
SIGNATURE=$(printf '%s.%s' "$TIMESTAMP" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')
curl -X POST "http://localhost:3000/webhooks/growwriter" \
-H "Content-Type: application/json" \
-H "X-GrowWriter-Event: ping" \
-H "X-GrowWriter-Delivery: 9d2f7c1e-6a3b-4c8d-b5e0-1f2a3b4c5d6e" \
-H "X-GrowWriter-Signature: t=$TIMESTAMP,v1=$SIGNATURE" \
--data "$BODY"Regola la porta e il percorso in base a dove gira il tuo server. Un
endpoint funzionante risponde pong con un 200. Ora cambia un solo
carattere dentro BODY ed esegui di nuovo: dovresti ricevere un 401, la
prova che la tua verifica della firma verifica davvero. Per provare un
articolo completo, sostituisci BODY con il
payload di esempio completo
(su una sola riga) e imposta i due header dell'evento su
article.published.
Per ricevere eventi veri da GrowWriter sulla tua macchina, ricorda che
l'URL dell'endpoint deve essere pubblico, quindi localhost non funzionerà.
Un servizio di tunnel come ngrok o cloudflared dà al tuo server locale un
indirizzo HTTPS pubblico temporaneo; usalo come URL dell'endpoint mentre
sviluppi, poi passa a quello vero.
Cosa contiene il payload
Ogni consegna è un POST con Content-Type: application/json, tre header
X-GrowWriter-* (nome dell'evento, id di consegna, firma) e un corpo JSON
che porta l'articolo in tre formati. Il
riferimento del payload è il
contratto: ogni header e ogni campo con il tipo, se è sempre presente, il
formato esatto e
l'esempio completo.
I campi che fanno il grosso del lavoro:
| Campo | Come usarlo |
|---|---|
article.id | La tua chiave stabile. Conservala in un campo dedicato e aggiorna il post esistente quando torna lo stesso id. |
article.slug | Dato di visualizzazione per l'indirizzo del post. Cambia quando si sceglie un nuovo titolo nel writer, quindi non usarlo mai come chiave. |
article.language | Codice lingua di due lettere (en, es, ...) per instradare i post su un sito multilingue. Ogni traduzione è un articolo a sé con il proprio id. |
article.content | L'articolo in tre formati: markdown, html pronto all'uso e blocks strutturati. Usa quello adatto al tuo sito. |
article.dates | firstPublishedAt è la tua data di pubblicazione, modifiedAt quella di aggiornamento. Prendi i primi dieci caratteri per i campi data senza orario. |
article.coverImage e article.images | La copertina più ogni immagine del testo in ordine. Scaricale e ridimensionale. |
article.keyword e article.relatedQuestions | Il termine di ricerca a cui punta l'articolo, e le domande che Google gli abbina. Campo SEO e materiale per le FAQ; nessuno dei due è un tag. |
article.brand | Nome e sito del tuo brand per il markup SEO, mai una persona. Imposta l'autore dei post nella tua logica di salvataggio. |
Nuovi campi opzionali possono comparire senza che la data version cambi,
quindi ignora i campi che non riconosci. I cambiamenti incompatibili fanno
avanzare la data e vengono annunciati prima nel changelog; la
policy completa è in
Versionamento.
Consegna e nuovi tentativi
Una pubblicazione su una connessione è una consegna, e una consegna fa
fino a tre tentativi (subito, poi 1 secondo e 3 secondi più tardi), ognuno
con 10 secondi di attesa per una risposta 2xx qualsiasi. Tutti i
tentativi condividono un solo id di consegna e vengono firmati di nuovo, i
redirect non vengono mai seguiti e GrowWriter non riprova più tardi: dopo il
terzo fallimento il canale mostra un pulsante Riprova nella
finestra di pubblicazione, che avvia una nuova consegna. Le regole esatte,
compresa la nota "almeno una volta" su cui si basa il tuo controllo dei
doppioni, sono nel riferimento:
Consegna.
Risoluzione dei problemi
Ogni errore ha la sua voce nella pagina di risoluzione dei problemi, organizzata in base a quello che vedi. I più comuni alla prima connessione:
- L'evento di prova fallisce con HTTP 301, 302, 303, 307 o 308
- "Il tuo endpoint non ha risposto in tempo"
- "Il tuo endpoint ha rifiutato la consegna"
- La prova riesce, ma il primo articolo fallisce con un 500 su Vercel o Netlify
- La prova riesce ma gli articoli veri falliscono
- L'articolo è online, ma il pannello del mio CMS mostra un errore di validazione
- Ho eliminato un articolo in GrowWriter, ma è ancora sul mio sito
Ancora bloccato? Scrivici dall'app e lo risolviamo insieme.
Changelog
- Versione attuale:
2026-09-06. Il payload descritto nel riferimento è il primo contratto pubblicato. I cambiamenti futuri saranno elencati qui, dal più recente: i cambiamenti additivi mantengono la data di versione, le rimozioni o le rinomine la fanno avanzare e vengono annunciate qui prima di andare online.
Avvio rapido dei webhook
Collega GrowWriter al tuo sito web e ricevi il tuo primo evento di prova firmato, in una sola pagina, con un solo file di codice.
Riferimento del payload webhook
Ogni header e ogni campo che GrowWriter invia al tuo endpoint webhook, con il tipo, se è sempre presente, il formato esatto e su cosa puoi contare.