Docs

Webhook-Payload-Referenz

Jeder Header und jedes Feld, das GrowWriter an deinen Webhook-Endpoint sendet, mit Typ, ob es immer vorhanden ist, exaktem Format und dem, worauf du dich verlassen kannst.

Zuletzt aktualisiert am

Diese Seite ist der Vertrag, geschrieben für die Person, die deinen Endpoint baut. Bist du das nicht, kannst du hier aufhören. Die Anleitung deckt die Einrichtung ab; diese Seite listet genau auf, was ankommt, damit niemand raten muss.

So liest du diese Seite

Jede Tabelle hat dieselben fünf Spalten:

  • Feld: der Pfad im JSON-Body, als Code. images[].url meint die url jedes Elements im Array images.
  • Typ: string, string[] (eine Liste von Strings), object, object[], number, boolean.
  • Vorhanden: eines von drei Wörtern. Immer heißt, der Schlüssel steckt in jeder Lieferung. Immer, kann leer sein heißt, der Schlüssel ist da, aber der Wert kann ein leerer String oder eine leere Liste sein. Fehlt, wenn nicht gesetzt heißt, der Schlüssel fehlt im JSON einfach, wenn es keinen Wert gibt. Er ist nie null.
  • Format und Garantien: wie der Wert aussieht und worauf du dich verlassen kannst. Wo etwas nicht erzwungen wird, sagt die Tabelle das.
  • Beispiel: ein realistisch aussehender Wert.

Zwei Regeln gelten überall. Erstens: Die neueste Lieferung ist maßgeblich. Unterscheidet sich ein Wert zwischen zwei Lieferungen desselben Artikels, nimm den neuesten. Zweitens: Ignoriere Felder, die du nicht kennst. Neue optionale Felder können ohne Versionswechsel auftauchen (siehe Versionierung).

Ein kleines Glossar für die Begriffe in den Tabellen: Ein ISO-8601-Datum sieht so aus: 2026-09-01T09:30:00.000Z (Jahr-Monat-Tag, ein T, die Uhrzeit bis zur Millisekunde und Z für UTC). Eine UUID ist eine zufällige Id wie 9d2f7c1e-6a3b-4c8d-b5e0-1f2a3b4c5d6e.

Die Anfrage

GrowWriter sendet pro Lieferung einen POST an die Endpoint-URL, die du eingegeben hast.

EigenschaftWert
MethodePOST
Content-Typeapplication/json
User-AgentGrowWriter-Webhooks/1.0
Body-GrößeKeine Obergrenze. Ein normaler Artikel hat üblicherweise ein paar hundert Kilobyte, weil der Inhalt dreimal reist (Markdown, HTML, Blöcke). Lange Artikel können 1 MB überschreiten.

Was als Erfolg zählt, wie Wiederholungen funktionieren und was du antworten sollst, steht unter Zustellung. Die Header, die du brauchst:

HeaderTypFormat und GarantienBeispiel
X-GrowWriter-Eventstringping, article.published, article.updated oder article.unpublished. Derselbe Wert wie event im Body.article.published
X-GrowWriter-DeliverystringUUID v4. Identisch über alle Versuche einer Lieferung. Derselbe Wert wie deliveryId im Body.9d2f7c1e-6a3b-4c8d-b5e0-1f2a3b4c5d6e
X-GrowWriter-Signaturestringt=<Unix-Sekunden>,v1=<64 Hex-Zeichen>. v1 ist der HMAC-SHA256 über "<t>.<roher Body>", mit deinem kompletten Signaturschlüssel. Bei jedem Versuch frisch.t=1756718400,v1=5257a8…

Zustellung

Eine Veröffentlichung an eine Verbindung ist eine Lieferung, und eine Lieferung macht bis zu drei Versuche, bevor sie aufgibt:

VersuchWann er passiert
1In dem Moment, in dem du veröffentlichst
21 Sekunde nachdem Versuch 1 fehlschlägt
33 Sekunden nachdem Versuch 2 fehlschlägt

Die Regeln, auf die sich dein Code verlassen kann:

  • Timeout. Jeder Versuch wartet bis zu 10 Sekunden auf deine Antwort.
  • Erfolg. Jeder 2xx-Status. Antworte mit dem JSON {"url": "https://…"}, um den veröffentlichten Beitrag zu benennen, und GrowWriter zeigt einen Link Beitrag ansehen darauf (siehe Die Antwort); jeder andere Erfolgs-Body wird ignoriert. Wenn ein Versuch abgelehnt wird, zeigt GrowWriter den Status und die ersten 200 Zeichen des Bodys in seinem Dialog, ein kurzer Grund als reiner Text hilft dir also beim Debuggen.
  • Fehlschlag. Ein Timeout, jedes 3xx (Weiterleitungen werden nie befolgt; jeder Versuch geht an exakt die URL, die du eingegeben hast) oder jeder andere Status.
  • Gleiche Id, frische Signatur. Alle Versuche teilen sich eine X-GrowWriter-Delivery-Id, und jeder Versuch wird mit eigenem Zeitstempel signiert, eine Wiederholung stolpert also nie über deine Fünf-Minuten-Prüfung.
  • Mindestens einmal. Ein langsames 2xx kann ankommen, nachdem GrowWriter schon wiederholt hat, dieselbe Liefer-Id kann dich also zweimal erreichen. Behandle eine wiederholte Id als bereits erledigt.
  • Nichts nach dem dritten Versuch. GrowWriter wiederholt später nichts. Der Kanal zeigt im Veröffentlichen-Dialog einen Button Erneut versuchen, der eine neue Lieferung mit einer neuen Id startet. Veröffentlichst du den Artikel später erneut, wird er ebenfalls erneut geliefert, als article.updated.
  • Rate. Das Veröffentlichen ist auf 20 Veröffentlichungsanfragen pro Stunde und Konto begrenzt, eine Anfrage pro Kanal. Das Testereignis ist auf 60 pro Stunde begrenzt. Die Lieferungen selbst haben kein Rate-Limit.

Das Testereignis (Testereignis senden im Verbindungsdialog) folgt denselben Regeln, ein Endpoint, der nie antwortet, braucht also etwa eine halbe Minute, bis er fehlschlägt.

Die Antwort

GrowWriter braucht von dir nur einen 2xx-Status. Optional sagst du ihm, wo der Beitrag live gegangen ist, damit der Veröffentlichen-Dialog darauf verlinken kann:

Erfolgsantwort (optionaler Body)
{ "url": "https://www.example.com/blog/mein-artikel" }

Die Regeln:

  • Absolute http- oder https-URL. Relative Pfade und jedes andere Schema werden ignoriert, weil der Wert als Link gerendert wird.
  • Andere Schlüssel werden ignoriert. Du kannst die url neben deinen eigenen Feldern zurückgeben.
  • Nichts ist jemals ein Fehler. Ein schlichtes OK, ein leerer Body, Nicht-JSON oder JSON ohne url zählen alle als Erfolg; der Dialog zeigt dann einfach keinen Link.
  • Halte es klein. GrowWriter liest die ersten 4 KB des Bodys.
  • Jede Lieferung ersetzt den Link. Gespeichert wird, was die letzte erfolgreiche Lieferung zurückgegeben hat. Antwortest du bei einer Neuveröffentlichung ohne url, verschwindet der Link.

Gib die url zurück, wenn dein Handler den Beitrag anlegt, bevor er antwortet. Antwortest du zuerst und legst den Beitrag in einem Hintergrund-Job an, kennst du die url noch nicht: Lass sie weg, und der Dialog zeigt für diese Veröffentlichung keinen Link.

Der Umschlag

Die Felder auf oberster Ebene, in jeder Lieferung vorhanden:

FeldTypVorhandenFormat und GarantienBeispiel
versionstringImmerEin Datum, das die Form des Payloads benennt. Ändert sich nur bei brechenden Änderungen; siehe Versionierung."2026-09-06"
eventstringImmerping, article.published, article.updated oder article.unpublished."article.published"
deliveryIdstringImmerUUID v4. Ein Wert pro Lieferung, geteilt von ihren Wiederholungen."9d2f7c1e-…"
sentAtstringImmerISO 8601, UTC, Millisekunden. Wann der Payload gebaut wurde (einmal pro Lieferung, nicht pro Versuch)."2026-09-01T09:30:00.412Z"

Die vier Ereignisse:

EreignisWann es ausgelöst wirdBody
pingDu klickst im Verbindungsdialog auf Testereignis senden.Nur der Umschlag
article.publishedDie erste erfolgreiche Lieferung eines Artikels an diese Webhook-Verbindung.Umschlag + der volle article
article.updatedJede spätere Veröffentlichung desselben Artikels an diese Verbindung, bearbeitet oder nicht.Umschlag + der volle article
article.unpublishedDer Artikel wird in GrowWriter gelöscht, oder diese Webhook-Verbindung wird getrennt (einmal pro Artikel, den sie veröffentlicht hatte).Umschlag + nur article.id, article.slug, article.language

„Diese Verbindung" ist entscheidend: Veröffentlichst du denselben Artikel an eine zweite Webhook-Verbindung, bekommt diese article.published, weil sie den Artikel noch nie gesehen hat. Ein erster Versuch, der fehlgeschlagen ist, zählt ebenfalls als nie gesehen, die Wiederholung ist also wieder article.published. Beide Veröffentlichungs-Ereignisse tragen den vollen Artikel, dein Handler kann sie also gleich behandeln: Lege den Beitrag mit article.id als Schlüssel an oder aktualisiere ihn. Bei article.unpublished versteckst oder löschst du den Beitrag mit dieser id; was von beidem, ist deine Entscheidung, und mit einer Entfernung reist kein Inhalt mit.

Das Ping-Ereignis

Testereignis senden im Verbindungsdialog liefert einen ping. Er enthält nur den Umschlag, kein article:

ping
{
  "version": "2026-09-06",
  "event": "ping",
  "deliveryId": "5f0c9a1e-4d2b-4f6a-9c3e-8b7d6a5e4f3c",
  "sentAt": "2026-09-01T09:30:00.000Z"
}

Das Unpublished-Ereignis

Einen Artikel in GrowWriter zu löschen oder den Webhook zu trennen liefert article.unpublished für jeden Artikel, den diese Verbindung veröffentlicht hatte. Es identifiziert den Beitrag und sonst nichts:

article.unpublished
{
  "version": "2026-09-06",
  "event": "article.unpublished",
  "deliveryId": "0b7d3e2a-9c41-4f58-8e6d-2a1b3c4d5e6f",
  "sentAt": "2026-09-03T08:12:44.000Z",
  "article": {
    "id": "cme8x2k9r0001l7042n5q8w3v",
    "slug": "how-to-choose-running-shoes-that-actually-fit",
    "language": "en"
  }
}

Verstecke oder lösche den Beitrag mit article.id als Schlüssel. Es gelten dieselben Zustellregeln (drei Versuche, dann nichts mehr), ein Empfänger, der nicht erreichbar war, verpasst die Entfernung also: Behandle ein späteres article.published für eine id, die du noch hältst, als den maßgeblichen Zustand.

article: Identität

FeldTypVorhandenFormat und GarantienBeispiel
article.idstringImmer25 Zeichen, Kleinbuchstaben und Ziffern, beginnt mit c. Sicher in URLs und Dateinamen. Ändert sich nie. Das ist dein Schlüssel."cme8x2k9r0001l7042n5q8w3v"
article.languagestringImmerZwei Kleinbuchstaben, ISO 639-1. Nie ein Regions-Tag (es, nie es-ES). Norwegisch ist nb. Standard ist en."es"
article.slugstringImmerKleinbuchstaben, aus dem Titel abgeleitet, wenn der Brief erzeugt wird. Nie leer. Nicht eindeutig, keine Maximallänge, kann nicht-lateinische Buchstaben enthalten."laufschuhe-finden"

Hinweise zur Identität:

  • Jede Sprache ist ein eigener Artikel. Eine Übersetzung ist ein separater Entwurf mit eigener id. Paare Sprachen mit deinen eigenen Daten, über article.language.
  • Die 41 möglichen Sprachcodes: 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.
  • Wie der Slug gebaut wird. Lateinische Akzente werden aufgelöst (qué wird zu que), Leerzeichen werden zu Bindestrichen, Satzzeichen fallen weg, und Buchstaben aus anderen Schriften bleiben erhalten, ein japanischer Titel ergibt also einen japanischen Slug. Percent-encode ihn, bevor du ihn in eine URL setzt (encodeURIComponent(article.slug) in JavaScript).
  • Wann sich der Slug ändert. Den Titel im Editor zu bearbeiten ändert ihn nicht. Einen neuen Titel im Writer zu wählen erzeugt Brief und Slug neu, ein article.updated kann also einen neuen Slug unter derselben id tragen.
  • Nutze nie den Slug als Schlüssel. Er kann sich bei einem Update ändern, und zwei Artikel können denselben haben. Speichere ihn als Anzeigedaten neben der id.

article: Textfelder

FeldTypVorhandenFormat und GarantienBeispiel
article.titlestringImmerUnicode. Keine Maximallänge. Wiederholt sich nicht im Inhalt, rendere ihn also selbst."Laufschuhe finden, die wirklich passen"
article.summarystringFehlt, wenn nicht gesetztReiner Text, ein Absatz. Keine Maximallänge."Ein praktischer Leitfaden für…"
article.metaDescriptionstringFehlt, wenn nicht gesetztReiner Text, geschrieben für 140 bis 160 Zeichen. Nie länger als 160: Alles darüber wird vor der Zustellung an einer Wortgrenze abgeschnitten."So findest du Laufschuhe…"
article.takeawaysstring[]Immer, kann leer seinSätze in reinem Text, 3 bis 5 wie erzeugt; behandle die Anzahl als unbegrenzt. Jeder ist "<Punkt>: <Detail>" oder nur <Detail>.["Passform schlägt Marke, jedes Mal: …"]
article.keywordstringFehlt, wenn nicht gesetztDer eine Suchbegriff, für den der Artikel geschrieben wurde, so wie der Autor ihn eingetippt hat. Für dein SEO-Tooling; kein Tag."laufschuhe finden"
article.relatedQuestionsstring[]Immer, kann leer seinGoogles „Ähnliche Fragen" zum Keyword, in Sprache und Land des Artikels, erfasst, als die Titel erzeugt wurden. Höchstens 8, in Googles Reihenfolge. FAQ-Material: Zeige sie unter dem Artikel oder als FAQPage-Markup. Der Artikel verspricht nicht, jede davon zu beantworten, und sie sind keine Tags.["wie oft sollte ich laufschuhe wechseln?"]

article.coverImage und article.images

Das Titelbild und jedes Inline-Bild teilen sich eine Objektform:

FeldTypVorhandenFormat und GarantienBeispiel
urlstringImmerÖffentliche https://-URL im Speicher von GrowWriter. Läuft derzeit nicht ab, ist aber kein dauerhafter Host: Lade sie herunter."https://….public.blob.vercel-storage.com/…"
altTextstringFehlt, wenn nicht gesetztText für Barrierefreiheit."Seitenansicht eines Läufers mitten im Schritt"
captionstringFehlt, wenn nicht gesetztSichtbare Bildunterschrift. Erscheint auch in content.markdown als kursiver Absatz direkt nach dem Bild."Ein kurzer Blick auf den Laufstil…"
filenamestringFehlt, wenn nicht gesetztVorgeschlagener Name ohne Endung. Nicht erzwungen: Der Generator wird um 3 bis 7 ASCII-Wörter in Kleinbuchstaben gebeten, mit Bindestrichen verbunden. Erzeuge eigene Namen aus article.id, wenn du Sicherheit brauchst."runner-gait-check"
sectionSlugstringFehlt, wenn nicht gesetztDer Gliederungsabschnitt, für den das Bild erzeugt wurde, oder ein user-…-Wert bei Bildern, die der Autor von Hand eingefügt hat. Beim Titelbild nie vorhanden. Nicht parsen, nicht als Schlüssel nutzen."section-1-4f1a4848"
attributionobjectFehlt, wenn nicht gesetzt{ authorName?, pageUrl?, licenseShortName?, licenseUrl? }, jedes Feld optional. Vorhanden bei Fotos von Wikimedia Commons. Wird in content.markdown auch als kursive Zeile unter der Bildunterschrift gerendert.siehe Beispiel

Regeln, die sich zwischen beiden unterscheiden:

  • article.coverImage fehlt, wenn nicht gesetzt. Es hat nie einen sectionSlug.
  • article.images ist immer vorhanden und kann leer sein. Es listet die Inline-Bilder in Dokumentreihenfolge auf und enthält das Titelbild nicht. Es gibt keine Höchstzahl.
  • sectionSlug ist über die gesamte Lebensdauer des Artikels stabil; nur das Neuerzeugen der Gliederung im Writer erzeugt neue Ids.

Bilddateien

  • Format: JPEG, PNG, WebP oder AVIF. Nie GIF oder SVG.
  • Größe: Dateien werden so gespeichert, wie sie geholt wurden, in Originalauflösung, ohne Neukodierung. Stockfotos haben häufig 1 bis 6 MB; manche Originale überschreiten 10 MB. Verkleinere und komprimiere sie für deine Website.
  • Stabilität: Ein Bild, das sich nicht geändert hat, behält in späteren Lieferungen exakt dieselbe URL. Ein ersetztes Bild bekommt eine neue URL. Alte URLs funktionieren weiter.

article.content

FeldTypVorhandenFormat und Garantien
article.content.markdownstringImmerDer Artikel als Markdown im CommonMark-Stil, von GrowWriter normalisiert. Siehe was übrig bleibt.
article.content.htmlstringImmerFertig einfügbares HTML, aus derselben Struktur gebaut. Siehe was das HTML enthält.
article.content.blocksobject[]ImmerDer Artikel als JSON-Baum für eigene Renderer. Siehe das Blocks-Format.

Alle drei tragen denselben Artikel. Nimm beim Rendern eines davon; der Titel steckt in keinem.

Was im Markdown übrig bleibt

content.markdown wird von GrowWriter aus der Artikelstruktur neu gerendert, ist also vorhersehbar:

  • Vorhanden: Überschriften # bis ######, Absätze, geordnete und ungeordnete Listen (verschachtelt mit zwei Leerzeichen), Zitate, eingezäunte Codeblöcke mit Sprache, ----Trennlinien, **strong**, *em*, ~~durchgestrichen~~, `code`, [Text](https://…), ![alt](url). Blöcke sind durch Leerzeilen getrennt.
  • Bilder: ![alt](url), dann ein kursiver Absatz mit der Bildunterschrift, dann, nur bei Fotos mit Quellenangabe, eine kursive Zeile wie *Photo: Jane Doe · [Source](url) · [CC BY-SA 4.0](license-url)*. Nur http(s)-Bildadressen bleiben erhalten; alles andere wird verworfen.
  • Nicht vorhanden: Tabellen, Fußnoten, Aufgabenlisten. Hat ein Autor sie getippt, kommen sie als normale Absätze an.
  • Rohes HTML: Hat ein Autor HTML in den Editor eingefügt, kommt es als wörtlicher Text an, nicht als Markup.
  • Escaping: Normale Prosa enthält {, } und <. Escape sie, wenn du das Markdown als MDX, JSX oder Template kompilierst.

Was das HTML enthält

content.html wird Element für Element gebaut, nie von irgendwo kopiert:

  • Verwendete Tags: h1 bis h6, p, ul, ol (mit start, wenn nötig), li, blockquote, pre, code (mit class="language-…"), hr, figure, img, figcaption, a, strong, em, del und ein span als Trenner innerhalb von Bildunterschriften. Keine anderen Tags, keine ids, keine Inline-Styles, keine Skripte.
  • Text ist immer escaped (&, <, >, ").
  • Links bleiben nur mit http://- oder https://-Adressen erhalten; alles andere wird zu reinem Text.
  • Bilder werden als <figure><img src alt><figcaption>…</figcaption></figure> gerendert, ohne figcaption, wenn es keine Bildunterschrift und keine Quellenangabe gibt. Ein Bild, dessen Adresse nicht http(s) ist, wird komplett verworfen.
  • Kein umschließendes Element und kein Doctype: Füge es so in deine Seite ein, wie es ist.

article.dates

FeldTypVorhandenFormat und GarantienBeispiel
article.dates.createdAtstringImmerISO 8601, UTC, Millisekunden. Wann der Entwurf in GrowWriter erstellt wurde."2026-08-28T14:00:00.000Z"
article.dates.firstPublishedAtstringImmerISO 8601. Wann der Artikel zum ersten Mal an diese Verbindung veröffentlicht wurde. In jeder späteren Lieferung identisch."2026-09-01T09:30:00.000Z"
article.dates.modifiedAtstringImmerISO 8601. Der Moment, in dem diese Veröffentlichung angefragt wurde. Rückt bei jeder Veröffentlichung vor, auch ohne Änderungen. Nie früher als firstPublishedAt."2026-09-01T09:30:00.000Z"

Wie die drei zusammenhängen:

  • Bei der ersten Lieferung sind firstPublishedAt und modifiedAt gleich.
  • modifiedAt ist nicht „zuletzt bearbeitet". Bearbeiten ohne erneutes Veröffentlichen ändert nichts; erneutes Veröffentlichen ohne Bearbeitung rückt es vor. Nutze das event, um eine erste Veröffentlichung von einem Update zu unterscheiden, keinen Datumsvergleich.
  • Reine Datumsfelder in einem CMS brauchen meist YYYY-MM-DD: Nimm die ersten zehn Zeichen.

article.brand

FeldTypVorhandenFormat und GarantienBeispiel
article.brandobjectFehlt, wenn nicht gesetztVorhanden, wenn der Artikel zu einem Brand-Kit gehört (der Normalfall).
article.brand.namestringImmer innerhalb von brandDer Anzeigename der Marke. Für Publisher- oder Organisations-Markup, kein Autor."Stride Lab"
article.brand.websiteUrlstringFehlt, wenn nicht gesetzthttps://-URL der Marken-Website."https://stridelab.example.com"

Der Payload nennt eine Marke, nie eine Person. Brauchen deine Beiträge eine Autorenzeile, setze sie in deiner eigenen Speicherlogik.

Das Blocks-Format

content.blocks ist der Artikel als JSON-Baum, für Websites, die Inhalte selbst rendern, statt fertigem HTML zu vertrauen. Es ist ein Array aus Block-Objekten, und jeder Block hat einen von sieben Typen:

BlockFelder
headinglevel (1 bis 6) und children (Inline-Knoten)
paragraphchildren (Inline-Knoten)
listordered (Boolean), optional eine start-Nummer und items; jedes Item hat ein eigenes blocks-Array, verschachtelte Listen erscheinen also als Listen-Blöcke innerhalb eines Items
blockquoteblocks: vollständige Blöcke innerhalb des Zitats
codeoptional language und code als ein String
hrsonst nichts; eine thematische Trennlinie
imagesrc, plus optional alt, caption, filename, sectionSlug und attribution (gleiche Bedeutung wie bei Bildern)

Wo immer ein Block children hat, sind das Inline-Knoten: text (mit text), code (mit text), link (mit href und children) sowie strong / em / del (jeweils mit children, Stile können sich also verschachteln).

Ein Absatz mit fettem Text sieht zum Beispiel so aus:

{
  "type": "paragraph",
  "children": [
    { "type": "text", "text": "Das ist das ganze Geheimnis: " },
    { "type": "strong", "children": [{ "type": "text", "text": "Passform zuerst" }] },
    { "type": "text", "text": ", alles andere danach." }
  ]
}

Vollständiger Beispiel-Payload

Eine komplette article.published-Lieferung, erzeugt von demselben Code, der deinen Endpoint aufrufen wird. Die Bild-URLs sind Platzhalter: Echte zeigen auf den öffentlichen Speicher von GrowWriter.

Versionierung

Die aktuelle Version ist 2026-09-06, und diese Seite beschreibt sie vollständig. Das Feld version ist ein Datum, das die Form des Payloads benennt. Neue optionale Felder können auftauchen, und ein Schlüssel, der bisher fehlte, kann anfangen zu erscheinen, ohne dass sich dieses Datum ändert. Schreibe deinen Endpoint also so, dass er unbekannte Felder ignoriert, statt sie abzulehnen. Innerhalb einer Version wird nichts entfernt, umbenannt oder im Typ geändert. Das Datum ändert sich nur bei einer brechenden Änderung, und jede solche Änderung wird im Changelog angekündigt, bevor sie deinen Endpoint erreicht.

Auf dieser Seite