Laktawan papunta sa nilalaman
Dokumentasyon

Delivery webhook: pagtanggap ng mga artikulo sa sarili mong site

Ang naka-sign na webhook na nagpapadala ng mga artikulo ng AutoPSEO sa iyong site: mga header, bawat event at payload, ang mga sagot na dapat ibigay, mga retry, mga naghihintay na command at IndexNow.

Property → Content → Saan inilalathala ang mga artikulo22 min na pagbasa

Ipinapadala ng AutoPSEO ang bawat artikulong ipinublish, ina-update o ibinaba nito sa sarili mong site bilang naka-sign na HTTPS POST. Iniimbak ng iyong site ang artikulo at nire-render ito gamit ang sarili nitong mga template, kaya nasa iyong sistema ang content at patuloy itong gumagana kahit umalis ka pa sa AutoPSEO. Ito ang reference para sa developer na gagawa ng receiver: ang pag-sign, bawat event kasama ang payload nito, ang mga sagot na binabasa ng AutoPSEO, mga retry, mga naghihintay na command at IndexNow.

I-set up ang webhook

  1. Buksan ang pahinang Content ng property at pumunta sa Where articles are published.
  2. Punan ang Webhook address on your site. Dapat itong https:// URL sa pampublikong host name: tinatanggihan ang mga IP address at localhost. Hindi sumusunod ang AutoPSEO sa mga redirect, kaya ibigay ang pinal na address.
  3. Punan ang Your site's address, halimbawa https://example.com. Nakapailalim dito ang path ng bawat content type, kaya ang artikulo ng type na nasa /answers ay matatagpuan sa https://example.com/answers/<slug>.
  4. Piliin ang Save and send a test. Gagawa ang AutoPSEO ng signing secret (whsec_ na sinusundan ng 48 hex character) at magpi-ping sa iyong address. Mabibigo ang unang ping na ito sa receiver na nagsusuri ng mga signature, dahil wala pa itong secret.
  5. Ipakita ang Signing secret gamit ang Show o Copy, itago ito bilang secret sa iyong site, saka piliin ang Send a test.
  6. Basahin ang What your site handles: ipinapakita ng bawat command ang Connected, Not connected, Declared, not tested o Not tested.

Nagsisimula ang mga bagong property na ang AutoPSEO mismo ang nagho-host ng mga artikulo sa sarili nitong address (tingnan ang Hosting), at maaaring sabay na tumakbo ang hosting at ang webhook. Kapag ipinapakita na ng iyong site ang mga artikulo, piliin ang Stop hosting on AutoPSEO: lilipat sa address ng iyong site ang bawat live na artikulo at ipapadala ito sa iyong webhook bilang article.published. Kailangan ng button na iyon ng webhook na sumagot sa huling test nito at ng address ng iyong site. Habang naka-off ang hosting, hindi maaaring alisin ang webhook.

Agad na pinapalitan ng New secret ang signing secret at agad na titigil sa pag-verify ang luma. Ilagay ang bagong secret sa iyong site kaagad pagkagawa mo nito.

Nagpapila lang ang AutoPSEO ng mga delivery para sa webhook na ang huling ping ay sinagot ng 2xx. Ang bigong ping (sa pag-save, sa Send a test, o sa simula ng site scan) ay nagmamarka sa webhook na may error, at ang mga artikulong ipinublish habang may error ito ay hindi ipinapapila para rito. Panatilihing sumasagot ang ping, at piliin ang Send a test pagkatapos mong ayusin ang isang outage.

Mga header at signature

Ang bawat request ay isang POST na may JSON body at ang mga sumusunod na header:

HeaderValue
Content-Typeapplication/json
User-AgentAutoPSEO-Webhook/1
X-AutoPSEO-EventAng pangalan ng event, nasa body rin bilang event
X-AutoPSEO-DeliveryAng delivery id, nasa body rin bilang id. Pareho sa bawat retry ng iisang delivery
X-AutoPSEO-TimestampUnix time sa segundo noong na-sign ang attempt na ito
X-AutoPSEO-Signaturesha256= na sinusundan ng hex HMAC-SHA256 ng <timestamp>.<raw body>

Para i-verify ang isang request:

  1. Kunin ang timestamp header, isang tuldok, at ang raw na request body nang eksakto kung paano ito dumating, bago ang anumang JSON parsing.
  2. Kalkulahin ang HMAC-SHA256 sa string na iyon. Ang key ay ang buong signing secret ayon sa pagpapakita ng app, kasama ang prefix na whsec_, bilang UTF-8 bytes.
  3. I-hex-encode ang resulta at ihambing ito sa bahagi ng X-AutoPSEO-Signature pagkatapos ng sha256=, sa constant time. Tanggihan ang request kapag hindi magkapareho ang dalawa.

Bagong nasa-sign ang bawat attempt na may sarili nitong timestamp, kaya maaari mong tanggihan ang mga request na ang timestamp ay malayo sa iyong orasan (karaniwang window ang limang minuto) nang hindi nasisira ang mga retry. Ang HMAC mismo ay inilalarawan sa RFC 2104.

Ang bawat body ay may id, event, occurredAt (ISO 8601, UTC) at site: { propertyId, domain }.

Mga event

EventUriKailan ito ipinapadala ng AutoPSEO
pingCheckKapag sine-save mo ang webhook, sa Send a test, at sa simula ng bawat site scan
testCheckKaagad pagkatapos ng matagumpay na ping, isa para sa bawat command
article.publishedDeliveryUnang beses na naging live ang isang artikulo; bawat live na artikulo kapag huminto ka sa pag-host
article.updatedDeliveryMay bagong revision na naging live (refresh, optimisation, rollback, muling pag-publish pagkatapos ng takedown, pagdagdag ng mga wika) o may nadagdag na cover
article.unpublishedDeliveryIbinaba ang isang artikulo, o pinagsama sa ibang pahina
path.addDeliveryMay bagong value ng typed content type na naaprubahan
path.removeDeliveryMay humiling na ibaba ang path ng isang value
path.checkCallBago magplano ang AutoPSEO ng pahina sa address na walang pahina ang iyong site, o magmungkahi ng bagong value
site.exportCallSa site scan, sa araw-araw na topic sync, at bago baguhin ng AutoPSEO ang isang live na artikulo
articles.adoptedCallPagkatapos mag-import ang scan ng mga artikulong na-export ng iyong site
site.handoverCallKapag may nag-hand over ng pagsusulat ng mga content type sa AutoPSEO, o pabalik

Dumadaan ang mga delivery sa isang outbox: nire-record ang mga ito sa parehong database batch ng pagbabago, ipinapadala agad, nire-retry, at maaaring maghintay para sa iyong site. Ang mga call ay payak na request at response: binabasa ng AutoPSEO ang sagot sa mismong sandaling iyon. Ipinaliwanag sa Typed pages and new paths kung kailan ipinapadala ang mga path command.

Ang article payload

Dala ng article.published at article.updated ang buong artikulo, kasama ang bawat wika:

{
  "id": "0f6d2c1e-7a51-4c1b-9d0e-5b8f3a2e9c41",
  "event": "article.published",
  "occurredAt": "2026-10-12T08:30:00.000Z",
  "site": { "propertyId": "prop_123", "domain": "example.com" },
  "article": {
    "id": "a4c09e2b-6f1d-4e57-8b3a-1d2e3f4a5b6c",
    "slug": "plan-a-content-calendar",
    "path": "/answers/plan-a-content-calendar",
    "type": { "key": "answers", "name": "Answers", "path": "/answers" },
    "lang": "en",
    "langs": ["en", "de"],
    "rev": 1,
    "url": "https://example.com/answers/plan-a-content-calendar",
    "datePublished": "2026-10-12",
    "dateModified": "2026-10-12",
    "contentHash": "9f2c4e1a7b3d5f6081a2c3d4",
    "writer": "platform",
    "category": "Planning",
    "copy": {
      "title": "How to plan a content calendar",
      "metaDescription": "A content calendar in five steps, with a template.",
      "h1": "How to plan a content calendar",
      "directAnswer": "List your topics, give each a date and an owner, then review the plan weekly.",
      "keyTakeaways": ["Plan a quarter at a time.", "Give every piece one owner."],
      "sections": [{ "heading": "Start from your topics", "bodyHtml": "<p>Begin with the searches you want to win.</p>" }],
      "faq": [{ "q": "How far ahead should I plan?", "a": "One quarter is enough for most teams." }],
      "sources": [{ "title": "Creating helpful content", "url": "https://developers.google.com/search/docs/fundamentals/creating-helpful-content" }]
    },
    "translations": { "de": { "title": "Einen Redaktionsplan erstellen", "h1": "Einen Redaktionsplan erstellen" } },
    "cover": {
      "url": "https://app.autopseo.com/media/covers/prop_123/<sha256>.jpg",
      "sha256": "<sha256>",
      "contentType": "image/jpeg",
      "width": 1536,
      "height": 1024
    }
  }
}

Pinaikli rito ang German na entry; ang bawat translation ay may parehong hugis ng copy.

FieldKahulugan
idAng id ng AutoPSEO para sa artikulo. Hindi ito nagbabago, kaya ligtas itong gamitin bilang key
slugAng huling bahagi ng address. Sa mga typed page, naglalaman ito ng mga segment na pinagdugtong ng /, gaya ng google/software-engineer
pathAng address na kaugnay ng iyong site: ang path ng type kasama ang slug. Nakapirmi na kapag umiiral na ang pahina
type{ key, name, path }. Matatag ang key; ang path ay kung saan nakalagay ang mga artikulo ng type (walang laman para sa site root)
lang, langsAng pangunahing wika at bawat wika ng revision na ito
revAng numero ng revision
urlAng buong address ayon sa alam ng AutoPSEO: ang sa iyong site kapag naka-off na ang hosting, o ang huling url na isinagot ng iyong receiver
datePublished, dateModifiedYYYY-MM-DD. Nananatiling nakapirmi ang petsa ng publikasyon sa mga susunod na revision
contentHashIsang fingerprint ng revision
writerSino ang sumulat ng revision: platform (AutoPSEO), mcp (sarili mong AI), user, import (sariling teksto ng iyong site) o aggregate (isang list page)
copyAng artikulo sa pangunahing wika nito
translationsAng iba pang wika, naka-key ayon sa language code
categoryAng category ng iyong site para rito, kapag nag-iingat ng mga category ang iyong format
topic{ externalId, keyword } kapag isinulat ang artikulo para sa topic na ibinigay ng iyong site, para maisara mo ang topic na iyon
partsSa mga typed page, ang value ng bawat bahagi: { "company": "google", "role": "software-engineer" }
kind"list" sa isang list page. Ang copy nito ay { title, h1, items: [{ from, text }] }, kung saan ang from ay ang path ng pahinang pinagmulan ng bawat item, at ang parts ay nagpapangalan sa value na pinagsasama nito
coverIsang cover na ginawa ng AutoPSEO para sa artikulo. Kunin ito nang isang beses at magtabi ng sarili mong kopya; hindi nagbabago ang address dahil ang pangalan ng file ay ang SHA-256 ng imahe
basedOnSa article.updated: ang bersyon ng iyong pahina na pinagbatayan ng revision na ito (tingnan ang mga conflict sa ibaba)
redirectToSa article.unpublished pagkatapos ng merge: kung saan patungo na ang lumang address. Sagutin ito ng 301 at huwag maglagay ng anumang bago roon

Dala ng article.unpublished ang parehong mga field ng article maliban sa copy, translations, category at cover.

Sa loob ng copy

FieldKahulugan
title, metaDescription, h1Ang pamagat ng pahina, meta description at heading
directAnswerAng maikling sagot na unang ipinapakita
keyTakeawaysIsang listahan ng mga string
sections[{ heading, bodyHtml }]
faq[{ q, a }]
sources[{ title, url, lastVerified? }]
questionOpsyonal: ang tanong ng naghahanap sa sarili niyang mga salita, kapag hinihingi ito ng iyong format
categoryOpsyonal: isa sa mga category ng iyong site
listsOpsyonal: mga plain-text na listahan ayon sa key, para sa mga content type na humihingi ng mga ito (ang mga sample na tanong ng isang interview guide)

Sa mga artikulong isinusulat ng AutoPSEO, p, ul, li, strong, em, a at code lang ang ginagamit ng bodyHtml, at ang mga link ay nagtatabi lang ng kanilang href. Ang tekstong kinuha mula sa sarili mong site ay nagtatabi ng iyong HTML. Patakbuhin din ito sa sarili mong sanitiser kung kinakailangan ng iyong mga template.

Ano ang isinasagot ng iyong receiver

Anumang 2xx ay kumukumpleto sa isang delivery. Nagbabasa rin ang AutoPSEO ng opsyonal na JSON object mula sa body ng sagot; panatilihin itong wala pang 64 KB, dahil ang mas malaking sagot ay ibibilang na kabiguan.

FieldEpekto
urlAng https:// address kung saan nakalagay na ngayon ang artikulo. Nire-record ito ng AutoPSEO bilang URL ng artikulo
versionAng bersyon ng iyong pahina, isang opaque na string na hanggang 128 character. Babalik ito bilang basedOn sa susunod na update
urlsAng mga https:// address na binago ng delivery na ito, para sa IndexNow (bawat wika, o ang mga address na hindi mo na ibinibigay). Ang [] ay nangangahulugang walang nagbago
cover"wanted" kapag wala kang cover para sa artikulo at walang dala ang delivery. Gagawa ang AutoPSEO ng isa at ipapadala ito sa isang article.updated

Magkakaiba ang kahulugan ng mga sagot na error:

Ang sagot moPaano ito binabasa ng AutoPSEOAno ang mangyayari
Isang 4xx na may { "error": "unknown_event" }, o isang 501Hindi mo pa hinahawakan ang command na itoMaghihintay ang delivery, walang mga retry
Isang 409 na may { "error": "conflict" }Isinulat ang update mula sa bersyong wala na sa iyong pahinaMabibigo ang delivery nang walang mga retry, at babasahing muli ang artikulo mula sa iyong site bago ang susunod na pagbabago nito
Isang 4xx maliban sa 408 o 429 na may { "error": "<code>", "details": [] } sa path.add o path.removeIsang pagtanggi na may dahilan moNire-record; tingnan ang Typed pages and new paths
Anupamang iba pa: walang sagot sa loob ng 10 segundo, network error, redirect, 408, 429, 5xx o iba pang 4xxPansamantalang pagkabigoIre-retry nang may backoff

Mga retry at mga napalitang delivery

Ipinapadala ang isang delivery sa sandaling mai-save ang pagbabago, at nagpapadala ang iskedyul na limang minuto ng anumang due na, pinakamatanda muna. Pagkatapos ng pansamantalang pagkabigo, naghihintay ang AutoPSEO ng 1 minuto, saka 5 minuto, 15 minuto, 1 oras, 3 oras, 6 na oras at 12 oras sa pagitan ng mga attempt. Ang ikawalong bigong attempt ay nagmamarka sa delivery bilang failed, at binibilang ito ng pahinang Content sa pulang badge sa tabi ng webhook.

Kapag may mas bagong delivery para sa parehong artikulo, ang mas lumang delivery na nakabinbin pa ay minamarkahang superseded at hindi na ipapadala. Dala ng bawat delivery ang buong artikulo, kaya ang pinakabago na ang lahat ng kailangan ng iyong site. Ganito rin ang pagkilos ng mga path command tungkol sa parehong value.

Maaaring makita ng iyong receiver ang isang delivery nang dalawang beses, halimbawa kapag naiimbak nito ang artikulo pero sumagot ito pagkalipas ng 10-segundong timeout. I-deduplicate gamit ang X-AutoPSEO-Delivery, o gawing idempotent ang bawat write sa pag-iimbak ayon sa article.id. Maaari ring umabot ang article.updated sa artikulong binura mo pagkatapos ng article.unpublished, dahil nagpapadala ng update ang muling pag-publish ng artikulong ibinaba: ituring ang mga update bilang "gumawa o palitan".

Mga command na hindi pa hinahawakan ng iyong site

Maaaring ipatupad ng isang site ang protocol nang isang command bawat pagkakataon. Itinatago ng AutoPSEO ang estado ng bawat command at natututuhan ito sa tatlong paraan:

  • Mga test. Pagkatapos ng bawat matagumpay na ping, nagpapadala ang AutoPSEO ng isang test bawat command. Ang body ay { id, event: "test", occurredAt, site, test: { event: "<command>", ... } }, kung saan hawak ng test ang isang sample na kapareho ng hugis ng totoong command at tungkol sa <type path>/autopseo-connection-test, isang address na walang site na mayroon. Suriin ito gaya ng pagsusuri mo sa totoo, huwag baguhin ang anuman, at sumagot ng 2xx: { "test": "<command>", "ok": true } para sa Connected, { "test": "<command>", "ok": false, "error": "unknown_event" } para sa Not connected. Ang sagot na may ok: false at ibang error code, gaya ng pagbaba ng address na wala sa iyo, ay ibinibilang pa ring connected. Nasa ilalim ng test ang sample, hindi kailanman sa top level, kaya walang makikitang artikulo roon ang receiver na walang alam tungkol sa mga test.
  • Mga totoong delivery at call. Minamarkahan ng 2xx ang command bilang connected; minamarkahan ito ng unknown_event bilang not connected.
  • Ang sagot sa ping. Ang mga command na inilista ng iyong sagot sa autopseo.events ay lalabas bilang Declared, not tested hanggang sa may sabihing mas marami ang isang test o delivery.

Naghihintay ang delivery para sa command na hindi connected. Inililista ng pahinang Content ang mga naghihintay, halimbawa "Your site does not take articles down yet", na may button na Done on the site para sa anumang isinagawa mo nang mano-mano; hindi na iyon ipapadala ng AutoPSEO kailanman. Kapag nakita ng susunod na test o delivery na connected na ang command, ipapadala ang lahat ng naghihintay rito.

Ang sagot sa ping

Sagutin ang ping ng 2xx. Ang site na tumatanggap lang ng mga artikulo ay walang ibang kailangan. Para ipaalam sa AutoPSEO ang iba pang ginagawa ng iyong site, magsama ng autopseo object:

{
  "ok": true,
  "autopseo": {
    "version": 1,
    "events": ["article.published", "article.updated", "article.unpublished"],
    "covers": true,
    "indexNow": { "key": "3f1c9a7e5b2d4f60", "keyLocation": "https://example.com/3f1c9a7e5b2d4f60.txt" },
    "export": ["articles", "topics", "profile"],
    "adopt": true,
    "handover": true,
    "types": ["answers"],
    "writer": "site"
  }
}
FieldKahulugan
version1. Binabalewala ang object kung walang version na 1 o higit pa
eventsAng mga command na hinahawakan mo, ipinapakitang declared hanggang masubukan
coverstrue kapag nagpapakita ang iyong mga pahina ng cover sa bawat artikulo. Gagawa na ng cover ang writer ng AutoPSEO habang nagsusulat, kaya dala na ito ng unang delivery, at hihilingan ang sarili mong AI na magbigay ng isa. Tingnan ang Covers
indexNow{ key, keyLocation? }: ang IndexNow key na ibinibigay mo (8 hanggang 128 titik, digit at gitling) at, kapag wala sa /<key>.txt ang key file, ang https:// na lokasyon nito
exportAng ibinibigay ng site.export: articles, topics, profile
adoptTinatanggap mo ang articles.adopted, kaya maaaring kunin ng buong scan ang iyong mga umiiral na artikulo
handoverTinatanggap mo ang site.handover
typesAng mga content type key na tinatanggap mo (hanggang 50)
writerSino ang sumusulat ng iyong mga bagong artikulo ngayon: site o autopseo

IndexNow para sa mga address na ibinibigay mo

Kapag idineklara ng iyong sagot sa ping ang indexNow, magpi-ping ang AutoPSEO sa IndexNow sa api.indexnow.org pagkatapos ng mga delivery na tinatanggap mo, at wala nang pini-ping ang iyong site mismo.

  • Ipinapadala nito ang mga urls na inilista ng iyong sagot. Kapag walang urls, ipinapadala nito ang url ng iyong sagot, at kung wala rin iyon, ang URL ng artikulo. Walang ipinapadala ang urls: [].
  • Mga address lang sa sarili mong host ang isinasama, ayon sa hinihingi ng IndexNow protocol: ang host ng keyLocation, kung wala ay ang host ng address ng iyong site.
  • Ang bawat delivery run ay nagpapadala ng isang ping bawat site, na may hanggang 10,000 address.
  • Tinatanggap ang 200 at 202. Ang 429, 5xx o walang sagot ay nagtatabi sa mga address at sumusubok muli pagkalipas ng isang oras, dinodoble sa bawat pagkakataon hanggang isang araw (mas matagal kapag hiniling ng Retry-After), kasama ang anumang naidagdag ng mga susunod na delivery. Ang 403 (hindi valid ang key) o 422 (ang mga address ay wala sa host) ay nire-record at inaalis.
  • Ipinapakita ng pahinang Content ang huling ping: kailan, ilang address, ang HTTP status at ang susunod na subok.
  • Habang ang AutoPSEO rin ang nagho-host ng mga artikulo, ang mga hosted na pahina ay pini-ping gamit ang sariling key ng AutoPSEO, at hindi ginagamit ang sa iyo.

Ang tool na IndexNow sa ilalim ng Indexing ay hiwalay at manu-manong submitter; tingnan ang IndexNow.

Mga conflict: ang teksto ng iyong site ang orihinal

Ang version ng iyong sagot (o ang version ng isang artikulo sa iyong export) ang pinaniniwalaan ng AutoPSEO na kasalukuyang bersyon ng iyong pahina. Dala ng bawat article.updated ito bilang basedOn. Kung nagbago ang pahina sa iyong site mula noon, halimbawa dahil sa pagwawasto ng isang editor, sumagot ng 409 na may { "error": "conflict" } at huwag mag-imbak ng anuman. Mabibigo ang delivery nang walang mga retry.

Bago isulat ang isang naka-queue na pagbabago sa live na artikulo, binabasa muli ng AutoPSEO ang artikulong iyon mula sa iyong site gamit ang site.export at paths, kapag hinahawakan ng iyong site ang site.export. Kung nauna na ang iyong bersyon, ang iyong teksto ang magiging susunod na revision ng artikulo (writer na import), at ang update na kasunod ay may dalang kasalukuyan mong bersyon bilang basedOn.

path.add at path.remove

Ang dalawang delivery na ito ay tungkol sa isang value ng isang typed content type, halimbawa isang kumpanya sa /interviews/{company}/{role}. May dala silang path object sa halip na article:

{
  "id": "7c1e9d2a-3b4f-4e8a-9c0d-1e2f3a4b5c6d",
  "event": "path.add",
  "occurredAt": "2026-10-12T08:30:00.000Z",
  "site": { "propertyId": "prop_123", "domain": "example.com" },
  "path": {
    "path": "/interviews/jane-street",
    "type": { "key": "interviews", "name": "Interviews", "path": "/interviews" },
    "part": "company",
    "value": {
      "value": "jane-street",
      "name": "Jane Street",
      "aliases": [],
      "url": "https://<its own site>/",
      "sameAs": ["https://<its careers page>/"],
      "description": "<what it is, in a few words>",
      "facts": [
        { "text": "<one sentence, as the source states it>", "source": "https://<the page that says it>", "lastVerified": "2026-10-01", "tier": "official" }
      ]
    },
    "takes": { "role": ["software-engineer"] }
  }
}
FieldKahulugan
pathAng sariling address ng value sa iyong site
typeAng content type, bilang { key, name, path }
partAng bahagi ng address na pinupunan ng value
value{ value, name }, at sa path.add ay kasama rin ang aliases, url, sameAs, description at facts na nakalap ng writer ng AutoPSEO. Ang bawat fact ay may text, source, lastVerified at tier na official o reported
takesSa path.add: ang mga value ng iba pang bahagi na hinahanap ng mga tao kasama nito
redirectToSa path.remove: kung saan dapat patungo ang mga address ng value mula ngayon, gamit ang 301

Sumagot ng 2xx kapag tinanggap mo ang command; sa path.add maaari kang magdagdag ng url kung saan nakalagay na ngayon ang value, at sa path.remove ng mga urls na hindi mo na ibinibigay, na mapupunta sa IndexNow. Para tumanggi, sumagot ng 4xx na may { "error": "<code>", "details": ["<why>"] }. Sa path.add, ibinabalik ng invalid_value ang value sa writer kasama ang iyong dahilan (pinal ang ikatlong pagtanggi) at ang anumang ibang code ay tumatanggi rito nang tuluyan. Sa path.remove, ibinibilang na tapos ang not_found, at ang anumang ibang code, gaya ng 409 in_use, ay nag-iiwan ng lahat gaya ng dati. Ang pagmarka sa isang naghihintay na path command bilang Done on the site ay ibinibilang na oo ng iyong site.

Mga call: mga request na binabasa ng AutoPSEO ang sagot

Sina-sign ang mga call gaya ng mga delivery. Naghihintay ang isang call nang hanggang 60 segundo bilang default at 30 segundo para sa mga muling pagbasa at path check, at nagbabasa ito ng mga sagot na hanggang 24 MB. Anumang sagot na hindi 2xx ay nagpapabigo sa call.

site.export

May dala ang request na export: { what, cursor, limit }, kasama ang since (isang ISO time) para sa mga topic o paths para sa muling pagbasa. Sumagot ng { "items": [], "next": "<cursor>" }; magtatanong muli ang AutoPSEO na ang cursor ay nakatakda sa iyong next hanggang sa ito ay walang laman o null.

  • Tumatakbo ang what: "articles" sa mga buong site scan, 10 bawat pahina, kapag nagdeklara ka ng export na may articles at adopt: true. Ang sagot na 400 ay nangangahulugang "hindi suportado". Sa paths: ["/answers/x"] at limit: 1, muling binabasa nito ang isang artikulo; sumagot ng artikulong iyon lang.
  • Tumatakbo ang what: "topics" sa bawat scan at sa araw-araw na sync, 50 bawat pahina; ang araw-araw na sync ay nagpapadala ng since, ang oras ng huling pull.
  • Tumatakbo ang what: "profile" sa bawat scan. Sumagot ng { "profile": { types, languages, rules, categories, format } }, bawat isa ay opsyonal: ang types bilang [{ key, name, path }], at ang rules na may brandTerms, synonyms, noise, onTopic at offTopic.

Kailangan ng na-export na artikulo ng externalId, isang slug (mga titik at digit ng anumang script at mga gitling, hanggang 200 character, mga segment na pinagdugtong ng /), isang path na nagtatapos sa slug na iyon, at copy na may kahit title, h1 at isang section. Mga opsyonal na field: lang (default ay en), type (isang content type key), url, keyword, category, datePublished, dateModified, version, translations, owner (ang article id ng AutoPSEO, kapag na-adopt na) at maintenance: { kind: "refresh" or "optimize", reason }, na nagpapapila sa gawaing iyon. Ang bawat na-export na artikulo ay nagiging managed na artikulo na ang iyong teksto ang unang revision, at walang ipinapadalang pabalik. Ligtas ang muling pag-export, at ang artikulong ibinaba ng AutoPSEO ay hindi na kailanman bumabalik sa pamamagitan ng export.

Ang isang topic item ay { keyword, status?, volume?, source?, externalId?, type?, reason? }, kung saan ang status ay queued, planned o rejected.

articles.adopted

Pagkatapos mag-import, hihilingin ng AutoPSEO na hayaan mo itong pamahalaan ang mga bagong artikulo: { "articles": [{ "externalId", "articleId", "slug", "path" }] }. Sumagot ng { "adopted": ["<externalId>"], "refused": [{ "externalId": "<id>", "reason": "<why>" }] }. Ang artikulong hindi mo kinumpirma ay aalisin muli sa AutoPSEO, maliban kung nakasulat na ang AutoPSEO ng revision nito.

site.handover

{ "handover": { "types": ["answers"], "writer": "autopseo" } }, ipinapadala kapag may pumili ng Let AutoPSEO write them o Hand writing back to the site sa pahinang Your site ng property. Sumagot ng { "ok": true, "writer": "autopseo" }. Pagkatapos ng hand-over sa AutoPSEO, ipinapasa ng iyong site sa AutoPSEO ang mga topic na sinusuri nito sa halip na isulat ang mga ito.

path.check

{ "paths": [{ "path": "/interviews/google/data-engineer" }, { "path": "/interviews/jane-street", "new": { "part": "company", "value": "jane-street", "name": "Jane Street" } }] }. Sumagot ng isang resulta bawat path: { "results": [{ "path": "/interviews/google/data-engineer", "ok": true }, { "path": "/interviews/jane-street", "ok": false, "error": "not_allowed", "details": ["We only list companies with a careers page."] }] }. Itinatago ang pagtanggi kasama ang iyong dahilan at hindi na itatanong muli; ang path na iniwan mo ay itatanong muli sa ibang pagkakataon.

Isang minimal na receiver

Isang Cloudflare Worker na nagve-verify ng signature gamit ang Web Crypto API, humahawak sa tatlong article command, at tumatanggi sa iba bilang unknown_event. Nag-iingat ito ng mga artikulo sa isang KV namespace na naka-bind bilang ARTICLES, at ang signing secret sa isang Worker secret na pinangalanang AUTOPSEO_SECRET.

const HANDLED = ["article.published", "article.updated", "article.unpublished"];
const MAX_SKEW_SECONDS = 300;
const SITE = "https://example.com";

const json = (data, status = 200) =>
  new Response(JSON.stringify(data), { status, headers: { "content-type": "application/json" } });

function hexToBytes(hex) {
  if (!/^[0-9a-f]{64}$/.test(hex)) return null;
  const out = new Uint8Array(32);
  for (let i = 0; i < 32; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
  return out;
}

async function verified(request, body, secret) {
  const timestamp = request.headers.get("X-AutoPSEO-Timestamp") || "";
  const signature = (request.headers.get("X-AutoPSEO-Signature") || "").replace(/^sha256=/, "");
  if (!/^\d+$/.test(timestamp)) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > MAX_SKEW_SECONDS) return false;
  const bytes = hexToBytes(signature);
  if (!bytes) return false;
  const enc = new TextEncoder();
  const key = await crypto.subtle.importKey("raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["verify"]);
  // verify() compares in constant time.
  return crypto.subtle.verify("HMAC", key, bytes, enc.encode(`${timestamp}.${body}`));
}

export default {
  async fetch(request, env) {
    if (request.method !== "POST") return new Response("Method not allowed", { status: 405 });
    const body = await request.text();
    if (!(await verified(request, body, env.AUTOPSEO_SECRET))) return json({ error: "bad_signature" }, 401);
    let msg;
    try {
      msg = JSON.parse(body);
    } catch {
      return json({ error: "bad_json" }, 400);
    }

    if (msg.event === "ping") return json({ ok: true, autopseo: { version: 1, events: HANDLED } });

    if (msg.event === "test") {
      const command = msg.test && msg.test.event;
      // Check the sample as you would the real command, and store nothing.
      return HANDLED.includes(command)
        ? json({ test: command, ok: true })
        : json({ test: command, ok: false, error: "unknown_event" });
    }

    if (msg.event === "article.published" || msg.event === "article.updated") {
      const a = msg.article;
      const stored = JSON.parse((await env.ARTICLES.get(a.id)) || "null");
      if (a.basedOn && stored && stored.version && stored.version !== a.basedOn) {
        return json({ error: "conflict", details: ["The page changed on the site."] }, 409);
      }
      // Any change to the page, here or in your own editor, gets a new version.
      const version = crypto.randomUUID();
      await env.ARTICLES.put(a.id, JSON.stringify({ ...a, version }));
      return json({ url: SITE + a.path, version });
    }

    if (msg.event === "article.unpublished") {
      const a = msg.article;
      if (a.redirectTo) await env.ARTICLES.put(a.id, JSON.stringify({ path: a.path, redirectTo: a.redirectTo }));
      else await env.ARTICLES.delete(a.id);
      return json({ ok: true });
    }

    return json({ error: "unknown_event" }, 400);
  },
};

Sa Node.js, mag-verify sa parehong paraan gamit ang createHmac at timingSafeEqual, sa raw body na natanggap ng iyong framework:

import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyAutopseo(rawBody, headers, secret) {
  const timestamp = String(headers["x-autopseo-timestamp"] || "");
  const given = String(headers["x-autopseo-signature"] || "");
  if (!/^\d+$/.test(timestamp) || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = "sha256=" + createHmac("sha256", secret).update(timestamp + ".").update(rawBody).digest("hex");
  return given.length === expected.length && timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}

Susunod: ang nakararating sa webhook ay napagpapasyahan ng pagsusulat at review at ng maintenance; ang buong daloy ay nasa content engine overview.

Ilagay sa autopilot ang iyong SEO content

Ikonekta ang Search Console at ang iyong site. Ini-scan ito ng AutoPSEO at agad na nagpaplano ng mga unang paksa. Hindi kailangan ng credit card.