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.
Ultimo aggiornamento il
Questa pagina è il contratto, scritto per chi costruisce il tuo endpoint. Se non sei tu, puoi fermarti qui. La guida copre la configurazione; questa pagina elenca esattamente cosa arriva, così nessuno deve tirare a indovinare.
Come leggere questa pagina
Ogni tabella ha le stesse cinque colonne:
- Campo: il percorso dentro il corpo JSON, in codice.
images[].urlindica laurldi ogni elemento dell'arrayimages. - Tipo:
string,string[](una lista di stringhe),object,object[],number,boolean. - Presente: una di tre espressioni. Sempre significa che la chiave
c'è in ogni consegna. Sempre, può essere vuoto significa che la chiave
c'è ma il valore può essere una stringa vuota o una lista vuota. Omesso
se assente significa che la chiave manca del tutto dal JSON quando non
c'è un valore. Non è mai
null. - Formato e garanzie: che aspetto ha il valore e su cosa puoi contare. Dove qualcosa non è garantito, la tabella lo dice.
- Esempio: un valore realistico.
Due regole valgono ovunque. Prima: l'ultima consegna è quella autorevole. Se un valore cambia tra due consegne dello stesso articolo, usa il più recente. Seconda: ignora i campi che non riconosci. Nuovi campi opzionali possono comparire senza un cambio di versione (vedi Versionamento).
Un piccolo glossario per i termini delle tabelle: una data ISO 8601 ha
questo aspetto: 2026-09-01T09:30:00.000Z (anno-mese-giorno, una T,
l'ora fino al millisecondo e Z che indica UTC). Un UUID è un id
casuale come 9d2f7c1e-6a3b-4c8d-b5e0-1f2a3b4c5d6e.
La richiesta
GrowWriter invia un POST per consegna all'URL dell'endpoint che hai
inserito.
| Proprietà | Valore |
|---|---|
| Metodo | POST |
Content-Type | application/json |
User-Agent | GrowWriter-Webhooks/1.0 |
| Dimensione del corpo | Nessun tetto. Un articolo standard pesa in genere qualche centinaio di kilobyte perché il contenuto viaggia tre volte (markdown, HTML, blocks). Gli articoli lunghi possono superare 1 MB. |
Cosa conta come successo, come funzionano i nuovi tentativi e cosa rispondere sono in Consegna. Gli header che ti servono:
| Header | Tipo | Formato e garanzie | Esempio |
|---|---|---|---|
X-GrowWriter-Event | string | ping, article.published, article.updated oppure article.unpublished. Stesso valore di event nel corpo. | article.published |
X-GrowWriter-Delivery | string | UUID v4. Identico in tutti i tentativi di una stessa consegna. Stesso valore di deliveryId nel corpo. | 9d2f7c1e-6a3b-4c8d-b5e0-1f2a3b4c5d6e |
X-GrowWriter-Signature | string | t=<secondi unix>,v1=<64 caratteri esadecimali>. v1 è l'HMAC-SHA256 di "<t>.<corpo grezzo>" con come chiave il tuo secret di firma intero. Nuovo a ogni tentativo. | t=1756718400,v1=5257a8… |
Consegna
Una pubblicazione su una connessione è una consegna, e una consegna fa fino a tre tentativi prima di arrendersi:
| Tentativo | Quando avviene |
|---|---|
| 1 | Nel momento in cui pubblichi |
| 2 | 1 secondo dopo il fallimento del tentativo 1 |
| 3 | 3 secondi dopo il fallimento del tentativo 2 |
Le regole su cui il tuo codice può contare:
- Timeout. Ogni tentativo aspetta la tua risposta fino a 10 secondi.
- Successo. Qualsiasi stato
2xx. Rispondi con il JSON{"url": "https://…"}per indicare il post pubblicato e GrowWriter mostra un link Vedi articolo che porta lì (vedi La risposta); qualsiasi altro corpo di successo viene ignorato. Quando un tentativo viene rifiutato, GrowWriter mostra nella finestra lo stato e i primi 200 caratteri del corpo, quindi un breve motivo in testo semplice ti aiuta a fare debug. - Fallimento. Un timeout, qualsiasi
3xx(i redirect non vengono mai seguiti; ogni tentativo va all'URL esatto che hai inserito) o qualsiasi altro stato. - Stesso id, firma nuova. Tutti i tentativi condividono un solo id
X-GrowWriter-Delivery, e ogni tentativo è firmato con il proprio timestamp, quindi un nuovo tentativo non fa mai scattare il tuo controllo dei cinque minuti. - Almeno una volta. Un
2xxlento può arrivare quando GrowWriter ha già riprovato, quindi lo stesso id di consegna può raggiungerti due volte. Tratta un id ripetuto come già completato. - Niente dopo il terzo tentativo. GrowWriter non riprova più tardi. Il
canale mostra un pulsante Riprova nella finestra di
pubblicazione, che avvia una nuova consegna con un id nuovo. Anche
ripubblicare l'articolo più avanti lo consegna di nuovo, come
article.updated. - Frequenza. La pubblicazione è limitata a 20 richieste di pubblicazione all'ora per account, una richiesta per canale. L'evento di prova è limitato a 60 all'ora. Le consegne in sé non hanno limiti di frequenza.
L'evento di prova (Invia evento di prova nella finestra di connessione) segue le stesse regole, quindi un endpoint che non risponde mai impiega circa mezzo minuto a fallire.
La risposta
A GrowWriter basta uno stato 2xx da parte tua. Se vuoi, digli dove è
andato online il post, così la finestra di pubblicazione può linkarlo:
{ "url": "https://www.example.com/blog/il-mio-articolo" }Le regole:
- URL assoluto
httpohttps. I percorsi relativi e qualsiasi altro schema vengono ignorati, perché il valore viene mostrato come link. - Le altre chiavi vengono ignorate. Puoi restituire la url insieme ai tuoi campi.
- Niente è mai un errore. Un semplice
OK, un corpo vuoto, un contenuto non JSON o un JSON senzaurlcontano tutti come successo; la finestra semplicemente non mostra nessun link. - Tienila piccola. GrowWriter legge i primi 4 KB del corpo.
- Ogni consegna sostituisce il link. Il link salvato è quello restituito dall'ultima consegna riuscita. Rispondi senza url a una ripubblicazione e il link sparisce.
Restituisci la url quando il tuo handler crea il post prima di rispondere. Se prima rispondi e poi crei il post in un job in background, la url non la conosci ancora: saltala, e la finestra non mostra nessun link per quella pubblicazione.
La busta
I campi di primo livello, presenti in ogni consegna:
| Campo | Tipo | Presente | Formato e garanzie | Esempio |
|---|---|---|---|---|
version | string | Sempre | Una data che dà un nome alla forma del payload. Cambia solo per cambiamenti incompatibili; vedi Versionamento. | "2026-09-06" |
event | string | Sempre | ping, article.published, article.updated oppure article.unpublished. | "article.published" |
deliveryId | string | Sempre | UUID v4. Un valore per consegna, condiviso dai suoi nuovi tentativi. | "9d2f7c1e-…" |
sentAt | string | Sempre | ISO 8601, UTC, millisecondi. Quando il payload è stato costruito (una volta per consegna, non per tentativo). | "2026-09-01T09:30:00.412Z" |
I quattro eventi:
| Evento | Quando scatta | Corpo |
|---|---|---|
ping | Fai clic su Invia evento di prova nella finestra di connessione. | Solo la busta |
article.published | La prima consegna riuscita di un articolo a questa connessione webhook. | Busta + l'article completo |
article.updated | Ogni pubblicazione successiva dello stesso articolo a questa connessione, modificato o no. | Busta + l'article completo |
article.unpublished | L'articolo viene eliminato in GrowWriter, oppure questa connessione webhook viene disconnessa (una volta per ogni articolo che aveva pubblicato). | Busta + solo article.id, article.slug, article.language |
"Questa connessione" conta: pubblicare lo stesso articolo su una seconda
connessione webhook invia article.published a quella connessione, perché
non ha mai visto l'articolo. Anche un primo tentativo fallito conta come
mai visto, quindi il nuovo tentativo è di nuovo article.published.
Entrambi gli eventi di pubblicazione portano l'articolo completo, quindi
il tuo handler può trattarli allo stesso modo: crea o aggiorna il post
usando article.id come chiave. Su article.unpublished, nascondi o
elimina il post con quell'id; quale dei due è una tua scelta, e con una
rimozione non viaggia nessun contenuto.
L'evento ping
Invia evento di prova nella finestra di connessione consegna un ping.
Contiene solo la busta, senza article:
{
"version": "2026-09-06",
"event": "ping",
"deliveryId": "5f0c9a1e-4d2b-4f6a-9c3e-8b7d6a5e4f3c",
"sentAt": "2026-09-01T09:30:00.000Z"
}L'evento unpublished
Eliminare un articolo in GrowWriter, o disconnettere il webhook, consegna
article.unpublished per ogni articolo che questa connessione aveva
pubblicato. Identifica il post e nient'altro:
{
"version": "2026-09-06",
"event": "article.unpublished",
"deliveryId": "0b7d3e2a-9c41-4f58-8e6d-2a1b3c4d5e6f",
"sentAt": "2026-09-03T08:12:44.000Z",
"article": {
"id": "cme8x2k9r0001l7042n5q8w3v",
"slug": "come-scegliere-scarpe-da-running",
"language": "it"
}
}Nascondi o elimina il post usando article.id come chiave. Valgono le
stesse regole di consegna (tre tentativi, poi niente), quindi un ricevitore
che era giù perde la rimozione: tratta un article.published successivo
per un id che hai ancora come lo stato autorevole.
article: identità
| Campo | Tipo | Presente | Formato e garanzie | Esempio |
|---|---|---|---|---|
article.id | string | Sempre | 25 caratteri, lettere minuscole e cifre, inizia con c. Sicuro negli URL e nei nomi di file. Non cambia mai. È la tua chiave. | "cme8x2k9r0001l7042n5q8w3v" |
article.language | string | Sempre | Due lettere minuscole, ISO 639-1. Mai un tag di regione (es, mai es-ES). Il norvegese è nb. Il valore predefinito è en. | "es" |
article.slug | string | Sempre | Minuscolo, derivato dal titolo quando viene generato il brief. Mai vuoto. Non unico, nessuna lunghezza massima, può contenere lettere non latine. | "come-scegliere-scarpe-da-running" |
Note sull'identità:
- Ogni lingua è un articolo a sé. Una traduzione è una bozza separata
con il proprio
id. Abbina le lingue con i tuoi dati, usandoarticle.language. - I 41 codici lingua possibili:
ar bg ca cs da de el en es et fa fi fr he hi hr hu hy id is it ja ko lt lv nb nl pl pt ro ru sk sl sr sv th tl tr uk vi zh. - Come si costruisce lo slug. Gli accenti latini vengono rimossi (
quédiventaque), gli spazi diventano trattini, la punteggiatura viene eliminata e le lettere di altri alfabeti vengono mantenute, quindi un titolo giapponese produce uno slug giapponese. Codificalo in percentuale prima di metterlo in un URL (encodeURIComponent(article.slug)in JavaScript). - Quando cambia lo slug. Modificare il titolo nell'editor non lo
cambia. Scegliere un nuovo titolo nel writer rigenera il brief e lo slug,
quindi un
article.updatedpuò portare un nuovo slug sotto lo stessoid. - Non usare mai lo slug come chiave. Può cambiare con un aggiornamento
e due articoli possono condividerne uno. Conservalo come dato di
visualizzazione accanto all'
id.
article: campi di testo
| Campo | Tipo | Presente | Formato e garanzie | Esempio |
|---|---|---|---|---|
article.title | string | Sempre | Unicode. Nessuna lunghezza massima. Non si ripete dentro il contenuto, quindi rendilo tu. | "Come scegliere scarpe da running che calzano davvero bene" |
article.summary | string | Omesso se assente | Testo semplice, un paragrafo. Nessuna lunghezza massima. | "Una guida pratica per…" |
article.metaDescription | string | Omesso se assente | Testo semplice scritto per stare tra 140 e 160 caratteri. Mai più lungo di 160: tutto ciò che eccede viene tagliato al confine di una parola prima della consegna. | "Impara a scegliere scarpe da running…" |
article.takeaways | string[] | Sempre, può essere vuoto | Frasi in testo semplice, da 3 a 5 come generate; tratta il numero come illimitato. Ognuna è "<punto>: <dettaglio>" oppure solo <dettaglio>. | ["La calzata batte la marca, sempre: …"] |
article.keyword | string | Omesso se assente | L'unico termine di ricerca per cui l'articolo è stato scritto, così come l'ha digitato l'autore. Per i tuoi strumenti SEO; non un tag. | "come scegliere scarpe da running" |
article.relatedQuestions | string[] | Sempre, può essere vuoto | Le domande "le persone hanno chiesto anche" di Google per la parola chiave, nella lingua e nel paese dell'articolo, catturate quando sono stati generati i titoli. Al massimo 8, nell'ordine di Google. Materiale per le FAQ: mostrale sotto l'articolo o come markup FAQPage. L'articolo non promette di rispondere a ognuna, e non sono tag. | ["ogni quanto vanno cambiate le scarpe da running?"] |
article.coverImage e article.images
La copertina e ogni immagine nel testo condividono la stessa forma di oggetto:
| Campo | Tipo | Presente | Formato e garanzie | Esempio |
|---|---|---|---|---|
url | string | Sempre | URL pubblico https:// sullo storage di GrowWriter. Oggi non scade, ma non è un hosting a lungo termine: scaricala. | "https://….public.blob.vercel-storage.com/…" |
altText | string | Omesso se assente | Testo per l'accessibilità. | "Vista laterale di un runner a metà falcata" |
caption | string | Omesso se assente | Didascalia visibile. Compare anche in content.markdown come paragrafo in corsivo subito dopo l'immagine. | "Un rapido controllo della falcata…" |
filename | string | Omesso se assente | Nome suggerito senza estensione. Non garantito: al generatore vengono chieste da 3 a 7 parole ASCII minuscole unite da trattini. Genera i tuoi nomi a partire da article.id se ti serve certezza. | "runner-gait-check" |
sectionSlug | string | Omesso se assente | La sezione della scaletta per cui l'immagine è stata generata, oppure un valore user-… per le immagini inserite a mano dall'autore. Mai sulla copertina. Non analizzarlo, non usarlo come chiave. | "section-1-4f1a4848" |
attribution | object | Omesso se assente | { authorName?, pageUrl?, licenseShortName?, licenseUrl? }, ogni campo opzionale. Presente per le foto di Wikimedia Commons. Renderizzato anche come riga in corsivo sotto la didascalia in content.markdown. | vedi esempio |
Regole che differiscono tra le due:
article.coverImageè omesso se assente. Non ha maisectionSlug.article.imagesè sempre presente e può essere vuoto. Elenca le immagini nel testo nell'ordine del documento e non include la copertina. Non c'è un numero massimo.sectionSlugè stabile per tutta la vita dell'articolo; solo rigenerare la scaletta nel writer produce nuovi id.
File immagine
- Formato: JPEG, PNG, WebP o AVIF. Mai GIF o SVG.
- Dimensioni: i file vengono conservati così come sono stati recuperati, alla risoluzione originale, senza ricodifica. Le foto stock pesano di solito da 1 a 6 MB; alcuni originali superano i 10 MB. Ridimensiona e comprimi per il tuo sito.
- Stabilità: un'immagine che non è cambiata mantiene esattamente lo stesso URL nelle consegne successive. Un'immagine sostituita riceve un URL nuovo. I vecchi URL continuano a funzionare.
article.content
| Campo | Tipo | Presente | Formato e garanzie |
|---|---|---|---|
article.content.markdown | string | Sempre | L'articolo come markdown in stile CommonMark, normalizzato da GrowWriter. Vedi cosa sopravvive. |
article.content.html | string | Sempre | HTML pronto da inserire, costruito dalla stessa struttura. Vedi cosa contiene l'HTML. |
article.content.blocks | object[] | Sempre | L'articolo come albero JSON per renderer personalizzati. Vedi il formato blocks. |
Tutti e tre portano lo stesso articolo. Scegline uno quando fai il render; il titolo non è in nessuno dei tre.
Cosa sopravvive nel markdown
content.markdown viene ri-renderizzato da GrowWriter a partire dalla
struttura dell'articolo, quindi è prevedibile:
- Presente: titoli da
#a######, paragrafi, liste ordinate e non ordinate (annidate con due spazi), citazioni, blocchi di codice delimitati con un linguaggio, interruzioni---,**strong**,*em*,~~barrato~~,`code`,[testo](https://…),. I blocchi sono separati da righe vuote. - Immagini:
, poi un paragrafo in corsivo con la didascalia, poi, solo per le foto con attribuzione, una riga in corsivo come*Photo: Jane Doe · [Source](url) · [CC BY-SA 4.0](license-url)*. Vengono mantenuti solo gli indirizzi immaginehttp(s); tutto il resto viene scartato. - Non presente: tabelle, note a piè di pagina, liste di attività. Se un autore le ha digitate, arrivano come paragrafi semplici.
- HTML grezzo: se un autore ha incollato HTML nell'editor, arriva come testo letterale, non come markup.
- Escape: la prosa normale contiene
{,}e<. Fai l'escape se compili il markdown come MDX, JSX o template.
Cosa contiene l'HTML
content.html è costruito elemento per elemento, mai copiato da qualche
parte:
- Tag usati: da
h1ah6,p,ul,ol(constartquando serve),li,blockquote,pre,code(conclass="language-…"),hr,figure,img,figcaption,a,strong,em,dele unospanusato come separatore dentro le didascalie. Nessun altro tag, nessun id, nessuno stile inline, nessuno script. - Il testo ha sempre l'escape (
&,<,>,"). - I link vengono mantenuti solo con indirizzi
http://ohttps://; tutto il resto diventa testo semplice. - Le immagini vengono renderizzate come
<figure><img src alt><figcaption>…</figcaption></figure>, senza ilfigcaptionquando non c'è né didascalia né attribuzione. Un'immagine il cui indirizzo non èhttp(s)viene scartata del tutto. - Nessun elemento contenitore e nessun doctype: inseriscilo nella tua pagina così com'è.
article.dates
| Campo | Tipo | Presente | Formato e garanzie | Esempio |
|---|---|---|---|---|
article.dates.createdAt | string | Sempre | ISO 8601, UTC, millisecondi. Quando la bozza è stata creata in GrowWriter. | "2026-08-28T14:00:00.000Z" |
article.dates.firstPublishedAt | string | Sempre | ISO 8601. Quando l'articolo è stato pubblicato per la prima volta su questa connessione. Identico in ogni consegna successiva. | "2026-09-01T09:30:00.000Z" |
article.dates.modifiedAt | string | Sempre | ISO 8601. Il momento in cui è stata richiesta questa pubblicazione. Avanza a ogni pubblicazione, anche senza modifiche. Mai precedente a firstPublishedAt. | "2026-09-01T09:30:00.000Z" |
Come si relazionano le tre date:
- Alla prima consegna,
firstPublishedAtemodifiedAtsono uguali. modifiedAtnon è "ultima modifica". Modificare senza ripubblicare non cambia nulla; ripubblicare senza modifiche la fa avanzare. Usa l'eventper distinguere una prima pubblicazione da un aggiornamento, non un confronto tra date.- I campi data senza orario di un CMS di solito vogliono
YYYY-MM-DD: prendi i primi dieci caratteri.
article.brand
| Campo | Tipo | Presente | Formato e garanzie | Esempio |
|---|---|---|---|---|
article.brand | object | Omesso se assente | Presente quando l'articolo appartiene a un brand kit (il caso normale). | |
article.brand.name | string | Sempre dentro brand | Il nome visualizzato del brand. Per il markup publisher/organizzazione, non un autore. | "Stride Lab" |
article.brand.websiteUrl | string | Omesso se assente | URL https:// del sito web del brand. | "https://stridelab.example.com" |
Il payload identifica un brand, mai una persona. Se i tuoi post hanno bisogno di un autore, impostalo nella tua logica di salvataggio.
Il formato blocks
content.blocks è l'articolo come albero JSON, per i siti che fanno il
render del contenuto da soli invece di affidarsi all'HTML pronto. È un array
di oggetti block, e ogni blocco è di uno di questi sette tipi:
| Blocco | Campi |
|---|---|
heading | level (da 1 a 6) e children (nodi inline) |
paragraph | children (nodi inline) |
list | ordered (booleano), un numero start opzionale e items; ogni voce ha il proprio array blocks, quindi le liste annidate compaiono come blocchi lista dentro una voce |
blockquote | blocks: blocchi completi annidati dentro la citazione |
code | language opzionale e code come un'unica stringa |
hr | nient'altro; un'interruzione tematica |
image | src, più alt, caption, filename, sectionSlug e attribution, tutti opzionali (stesso significato che in immagini) |
Ovunque un blocco abbia children, quelli sono nodi inline: text (con
text), code (con text), link (con href e children) e strong /
em / del (ognuno con children, così gli stili possono annidarsi).
Un paragrafo con testo in grassetto, per esempio:
{
"type": "paragraph",
"children": [
{ "type": "text", "text": "Tutto il segreto è qui: " },
{ "type": "strong", "children": [{ "type": "text", "text": "prima la calzata" }] },
{ "type": "text", "text": ", tutto il resto dopo." }
]
}Payload di esempio completo
Una consegna article.published completa, generata dallo stesso codice che
chiamerà il tuo endpoint. Gli URL delle immagini sono segnaposto: quelli
veri puntano allo storage pubblico di GrowWriter.
Versionamento
La versione attuale è 2026-09-06, e questa pagina la descrive per intero.
Il campo version è una data che dà un nome alla forma del payload. Nuovi
campi opzionali possono comparire, e una chiave che era omessa può
iniziare a comparire, senza che quella data cambi, quindi scrivi il tuo
endpoint in modo che ignori i campi che non riconosce invece di
rifiutarli. Niente viene rimosso, rinominato o cambiato di tipo dentro una
versione. La data cambia solo per un cambiamento incompatibile, e ogni
cambiamento del genere viene annunciato nel
changelog prima di arrivare al tuo
endpoint.
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.
Risoluzione dei problemi dei webhook
Risolvi un evento di prova o una consegna non riusciti partendo dal sintomo, con i messaggi esatti che GrowWriter mostra, cosa significa ognuno e cosa fare.