Publicering via API
Publicer artikler på dit eget websted eller CMS med signerede API-kald.
Papigrows sender signerede POST-anmodninger til den HTTPS-modtager, du driver. Modtageren gemmer og udgiver artiklen og svarer derefter med dens offentlige URL.
Konfigurer API-publiceringTilslut dit publiceringsendpoint
Byg en modtager, der gemmer og publicerer artikler på dit websted. Tilføj endpoint og fælles hemmelighed i CMS-indstillingerne. Test og gem sender connection.test med tomme data; svar med HTTP 200 og ok: true. Kun ejere og administratorer kan ændre forbindelsen.
Brug et offentligt HTTPS-endpoint uden forespørgselsparametre, fragmenter eller indlejrede loginoplysninger.
En tilfældig hemmelighed er klar til brug. Du kan erstatte den med din egen på 32 til 256 tegn. Kopiér den til modtageren og opbevar den på serveren.
{
"version": 1,
"event": "connection.test",
"id": "<event-id>",
"data": {}
}
HTTP/1.1 200 OK
Content-Type: application/json
{ "ok": true }Lad modtageren acceptere både den gamle og den nye hemmelighed, mens du tester og gemmer. Papigrows bruger den gemte hemmelighed, indtil den nye forbindelse har bestået testen og er gemt. Fjern den gamle, når igangværende leveringer er afsluttet.
Anmodningsheadere
Disse headere følger både forbindelsestesten og artikelleveringerne. Headernavne skelner ikke mellem store og små bogstaver.
| Headernavn | Eksempelværdi | Betydning |
|---|---|---|
Content-Type | application/json | Anmodningens indhold i JSON. |
Accept | application/json | Der forventes en JSON-bekræftelse. |
X-Papigrows-Event-Id | <event-id> | Matcher konvoluttens id. |
X-Papigrows-Timestamp | <unix-seconds> | Unix-tid i sekunder for dette forsøg. |
X-Papigrows-Signature | v1=<hex-hmac-sha256> | v1= efterfulgt af hexadecimal HMAC-SHA256 af tidsstemplet + '.' + anmodningens nøjagtige rå indhold. |
Idempotency-Key | <event-id> | Samme stabile konvolut-id ved nye forsøg. |
Verificér hver anmodning
Verificér v1-signaturen med HMAC-SHA256 over tidsstemplet, et punktum og den præcise rå anmodningskrop før JSON fortolkes. Sammenlign i konstant tid, afvis tidsstempler mere end fem minutter gamle eller i fremtiden, og behold hemmeligheden på serveren. HTTPS og signatur erstatter ikke dubletbeskyttelse.
Eksempel på anmodning
POST /your-publishing-endpoint
Content-Type: application/json
X-Papigrows-Event-Id: <event-id>
X-Papigrows-Timestamp: <unix-seconds>
X-Papigrows-Signature: v1=<hex-hmac-sha256>
Idempotency-Key: <event-id>
{
"version": 1,
"event": "article.upsert",
"id": "<event-id>",
"data": {
"id": "<permanent-article-id>",
"version": 7,
"title": "Example",
"html": "<p>...</p>",
"slug": "example",
"excerpt": "...",
"coverImageUrl": "https://example.com/cover.jpg",
"locale": "en",
"hreflangAlternates": [
{ "locale": "sv", "url": "https://example.com/sv/blog/example" }
]
}
}Felter i anmodningens indhold
Felterne beskriver article.upsert. Ved connection.test er data tomt. Protokolversionen i konvolutten er adskilt fra artiklens indholdsversion.
| Feltnavn | Værditype | Medtages | Betydning |
|---|---|---|---|
version | integer | Altid | Protokolversion: 1 for det oprindelige format eller 2 efter en udtrykkelig opgradering af modtageren. |
event | string | Altid | Hændelsestypen: article.upsert eller connection.test. |
id | string | Altid | Stabilt leverings-id, som deles med begge identitetsheadere. Gem det for at undgå dobbelte skrivninger. |
data | object | Altid | Hændelsens indhold. Tomt ved connection.test. |
data.id | string | Altid | Permanent id for denne sprogvariant. Opret eller opdater med denne nøgle. |
data.version | integer | Altid | Gemt indholdsrevision som et ikke-negativt heltal. Afvis ældre revisioner. |
data.title | string | Altid | Artiklens titel. |
data.html | string | Altid | HTML til udgivelse, inklusive tilgængelige hreflang-links. |
data.slug | string | Når tilgængelig | Foreslået URL-slug. Behold en stabil offentlig URL ved senere ændringer. |
data.excerpt | string | Når tilgængelig | Artikeluddrag, når det findes. |
data.coverImageUrl | string | Når tilgængelig | Forsidebilledets URL, når den findes. |
data.locale | string | Når tilgængelig | Sprogkode for denne artikelvariant. |
data.hreflangAlternates | array | Når tilgængelig | Andre udgivne sprogvarianter, når de findes. |
data.hreflangAlternates[].locale | string | For hver sprogvariant | Sprogkode for den linkede variant. |
data.hreflangAlternates[].url | string | For hver sprogvariant | Den udgivne variants offentlige URL. |
Version 2 og kompatibilitet
Version 2 indeholder ren tekst, arbejdsområde- og målgruppe-id'er, resuméer, sektioner og tags. Modtageren skal udtrykkeligt bekræfte version 2 under forbindelsestesten. Eksisterende modtagere beholder version 1, indtil du vælger en anden version og tester og gemmer den.
{
"version": 2,
"event": "connection.test",
"id": "<event-id>",
"data": {}
}
HTTP/1.1 200 OK
Content-Type: application/json
{ "ok": true, "version": 2 }POST /your-publishing-endpoint
Content-Type: application/json
X-Papigrows-Event-Id: <event-id>
X-Papigrows-Timestamp: <unix-seconds>
X-Papigrows-Signature: v1=<hex-hmac-sha256>
Idempotency-Key: <event-id>
{
"version": 2,
"event": "article.upsert",
"id": "<event-id>",
"data": {
"id": "<permanent-article-id>",
"groupId": "<permanent-article-group-id>",
"workspaceId": "<owning-workspace-id>",
"primaryAudienceId": "<saved-audience-id>",
"businessAreaId": "<saved-business-area-id>",
"text": "...",
"summary": "...",
"section": "Advice",
"tags": ["Families"],
"version": 7,
"title": "Example",
"html": "<p>...</p>",
"slug": "example",
"excerpt": "...",
"coverImageUrl": "https://example.com/cover.jpg",
"locale": "en",
"hreflangAlternates": [
{ "locale": "sv", "url": "https://example.com/sv/blog/example" }
]
}
}Version 2 bevarer de oprindelige artikelfelter og tilføjer disse obligatoriske felter. Målgruppe-id’er er null for ældre artikler uden gemt målretning.
| Feltnavn | Værditype | Betydning |
|---|---|---|
data.groupId | UUID | Stabil gruppe, som deles af sprogversionerne. |
data.workspaceId | UUID | Id for artiklens gemte arbejdsområde. |
data.primaryAudienceId | UUID | null | Gemt primært målgruppe-id eller null for en ældre artikel uden målgruppe. |
data.businessAreaId | UUID | null | Gemt forretningsområde-id eller null, hvis det mangler. |
data.text | string | Ren tekst fra renset artikel-HTML, ikke oprindelig Markdown. Links hentes ikke. |
data.summary | string | null | Gemt artikelresumé eller null, hvis det mangler. |
data.section | string | null | Sektion gemt for denne publicering eller null. |
data.tags | string[] | Tags gemt for denne publicering; en tom liste betyder ingen tags. |
Offentligt indholdsfeed
Aktivér Papigrows API-forbindelsen for at vise publicerede artikler. Anmod udtrykkeligt om version 2; uden versionsvalg bevares det oprindelige svar. Private arbejdsområder og upublicerede artikler er utilgængelige.
/api/content/feed/<workspace-slug>?version=2&locale=en&limit=20Hver artikel indeholder gemte id’er, indholdsversion, ren tekst og resumé. Sektion og tags kommer kun fra den gemte publiceringsdestination. Tags er null, når dokumentationen mangler. Behold version=2 og locale ved hver anmodning med sidemarkør.
I det offentlige feed er text null, hvis konvertering ikke er tilgængelig. textUnavailable er body_too_large over 1 MiB eller conversion_failed ved konverteringsfejl; ellers er værdien null. HTML og sideinddeling bevares.
Bekræft publicering
Når artiklen er offentlig, svar med HTTP 200 eller 201, samme ID, status published og offentlig HTTPS-URL. Købekræftelser, HTTP 202 eller 204, omdirigeringer, kladder og ugyldige svar tæller ikke som publicering. Bekræft ikke før artiklen er gemt og publiceret.
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "<permanent-article-id>",
"status": "published",
"url": "https://example.com/blog/example"
}Levering og nye forsøg
article.upsert opretter eller opdaterer én sprogvariant med data.id som permanent nøgle. Gem data.version og hændelses-ID atomisk med ændringen; dubletter får den gemte bekræftelse, og ældre versioner må aldrig overskrive nyere indhold. Der foretages op til tre forsøg med fem sekunders frist hver. Netværksfejl, HTTP 429 og HTTP 5xx prøves igen efter 250 ms og 1 sekund. Krop og hændelses-ID forbliver ens, mens tidsstempel og signatur fornyes.
Leveringsindhold er begrænset til 1 MiB og JSON-svar til 16 KiB. Begræns anmodningens rå indhold, før du fortolker det.
Hvad integrationen dækker
Kun artikler, inklusive ændringer publiceret igen fra Papigrows. Emnehændelser, sletninger, baggrundskøer og læsning af eksterne ændringer er ikke inkluderet. Redigering via e-mailsvar kan ikke sikkert opdatere en webhook; rediger og publicer i Papigrows. Eksisterende abonnements-, kvalitets- og automatiseringsindstillinger gælder. Kontrollér modtageren før et manuelt nyt forsøg efter en usikker fejl; der er ingen uendelig løkke.
Eksempel på signaturverificering
Denne Node.js-hjælper verificerer kun autenticitet. Modtageren skal også begrænse anmodningsstørrelse, validere skemaet, håndtere dubletter og versioner i varig lagring, publicere artiklen og returnere bekræftelsen nedenfor.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyWebhook(headers, rawBody, secret) {
const timestamp = headers.get("x-papigrows-timestamp") ?? "";
const signature = headers.get("x-papigrows-signature") ?? "";
if (!/^\d{10}$/.test(timestamp)) return false;
const age = Math.floor(Date.now() / 1000) - Number(timestamp);
if (age < 0 || age > 300) return false;
if (!/^v1=[a-f0-9]{64}$/.test(signature)) return false;
const expected = createHmac("sha256", secret)
.update(timestamp + ".").update(rawBody).digest();
const received = Buffer.from(signature.slice(3), "hex");
if (received.length !== expected.length
|| !timingSafeEqual(received, expected)) return false;
try {
const event = JSON.parse(rawBody.toString());
return typeof event.id === "string"
&& /^[a-f0-9]{64}$/.test(event.id)
&& headers.get("x-papigrows-event-id") === event.id
&& headers.get("idempotency-key") === event.id;
} catch { return false; }
}