Belgeler
Teslimat webhook'u: makaleleri kendi sitenizde almak
AutoPSEO makalelerini sitenize ileten imzalı webhook: başlıklar, her olay ve payload, verilmesi gereken yanıtlar, yeniden denemeler, bekleyen komutlar ve IndexNow.
AutoPSEO, yayımladığı, güncellediği veya yayından kaldırdığı her makaleyi imzalı bir HTTPS POST isteğiyle kendi sitenize gönderir. Sitenizde makaleyi saklar ve kendi şablonlarınızla görüntülersiniz; böylece içerik sizin sisteminizde durur ve AutoPSEO'dan ayrılsanız bile çalışmaya devam eder. Bu sayfa, alıcıyı geliştiren kişi için bir başvuru kaynağıdır: imzalama, her olay ve payload'ı, AutoPSEO'nun okuduğu yanıtlar, yeniden denemeler, bekleyen komutlar ve IndexNow.
Webhook'u kurma
- Mülkün Content sayfasını açın ve Where articles are published bölümüne gidin.
- Webhook address on your site alanını doldurun. Adres, genel bir ana bilgisayar adı üzerinde
https://ile başlayan bir URL olmalıdır: IP adresleri velocalhostreddedilir. AutoPSEO yönlendirmeleri takip etmez, bu yüzden nihai adresi verin. - Your site's address alanını doldurun, örneğin
https://example.com. Her içerik türünün yolu bunun altında yer alır; yani/answersaltındaki bir türün makalesihttps://example.com/answers/<slug>adresinde bulunur. - Save and send a test seçeneğini seçin. AutoPSEO bir imzalama gizli anahtarı (
whsec_ve ardından 48 onaltılık karakter) oluşturur ve adresinize ping gönderir. İmzaları doğrulayan bir alıcı bu ilk ping'de başarısız olur, çünkü henüz gizli anahtara sahip değildir. - Signing secret değerini Show veya Copy ile görüntüleyin, sitenizde bir gizli anahtar olarak saklayın, ardından Send a test seçeneğini seçin.
- What your site handles bölümünü okuyun: her komut Connected, Not connected, Declared, not tested veya Not tested olarak görünür.
Yeni mülklerde makaleleri AutoPSEO kendi adresinde barındırır (Barındırma sayfasına bakın); barındırma ve webhook birlikte çalışabilir. Siteniz makaleleri göstermeye başladığında Stop hosting on AutoPSEO seçeneğini seçin: yayındaki tüm makaleler sitenizin adresine taşınır ve webhook'unuza article.published olarak gönderilir. Bu düğme, son testine yanıt vermiş bir webhook ve sitenizin adresini gerektirir. Barındırma kapalıyken webhook kaldırılamaz.
New secret imzalama gizli anahtarını hemen değiştirir ve eski anahtar anında doğrulamayı bırakır. Yeni gizli anahtarı oluşturur oluşturmaz sitenize yerleştirin.
AutoPSEO, yalnızca son ping'i 2xx ile yanıtlanmış bir webhook için teslimat kuyruğa alır. Başarısız bir ping (kaydederken, Send a test ile veya bir site taramasının başında) webhook'u hatalı olarak işaretler; hatalı durumdayken yayımlanan makaleler onun için kuyruğa alınmaz. ping isteğine yanıt vermeyi sürdürün ve bir kesintiyi giderdikten sonra Send a test seçeneğini seçin.
Başlıklar ve imza
Her istek, JSON gövdeli bir POST isteğidir ve şu başlıkları taşır:
| Başlık | Değer |
|---|---|
Content-Type | application/json |
User-Agent | AutoPSEO-Webhook/1 |
X-AutoPSEO-Event | Olay adı; gövdede event olarak da bulunur |
X-AutoPSEO-Delivery | Teslimat kimliği; gövdede id olarak da bulunur. Bir teslimatın her yeniden denemesinde aynıdır |
X-AutoPSEO-Timestamp | Bu denemenin imzalandığı Unix zamanı (saniye cinsinden) |
X-AutoPSEO-Signature | sha256= ve ardından <timestamp>.<raw body> dizesinin onaltılık HMAC-SHA256 değeri |
Bir isteği doğrulamak için:
- Zaman damgası başlığını, bir nokta işaretini ve istek gövdesini, JSON ayrıştırmasından önce, geldiği haliyle birleştirin.
- Bu dize üzerinden HMAC-SHA256 hesaplayın. Anahtar, uygulamanın gösterdiği haliyle imzalama gizli anahtarının tamamıdır;
whsec_öneki dahil, UTF-8 baytları olarak kullanılır. - Sonucu onaltılık biçimde kodlayın ve
X-AutoPSEO-Signaturedeğerindesha256=sonrasındaki kısımla sabit zamanlı bir karşılaştırmayla kıyaslayın. İkisi farklıysa isteği reddedin.
Her deneme, kendi zaman damgasıyla yeniden imzalanır; bu nedenle zaman damgası saatinizden çok farklı olan istekleri (beş dakika yaygın bir penceredir) yeniden denemeleri bozmadan reddedebilirsiniz. HMAC'in kendisi RFC 2104 içinde tanımlanmıştır.
Her gövde id, event, occurredAt (ISO 8601, UTC) ve site: { propertyId, domain } alanlarını taşır.
Olaylar
| Olay | Tür | AutoPSEO bunu ne zaman gönderir |
|---|---|---|
ping | Kontrol | Webhook'u kaydettiğinizde, Send a test ile ve her site taramasının başında |
test | Kontrol | Başarılı bir ping'in hemen ardından, komut başına bir tane |
article.published | Teslimat | Bir makale ilk kez yayına girdiğinde; barındırmayı durdurduğunuzda yayındaki her makale |
article.updated | Teslimat | Yeni bir revizyon yayına girdiğinde (yenileme, optimizasyon, geri alma, yayından kaldırmanın ardından yeniden yayımlama, dil ekleme) veya bir kapak eklendiğinde |
article.unpublished | Teslimat | Bir makale yayından kaldırıldığında ya da başka bir sayfayla birleştirildiğinde |
path.add | Teslimat | Türlendirilmiş bir içerik türünün yeni bir değeri onaylandığında |
path.remove | Teslimat | Biri bir değerin yolunun kaldırılmasını istediğinde |
path.check | Çağrı | AutoPSEO, sitenizde sayfası bulunmayan bir adrese sayfa planlamadan veya yeni bir değer önermeden önce |
site.export | Çağrı | Bir site taraması sırasında, günlük konu eşitlemesinde ve AutoPSEO yayındaki bir makaleyi değiştirmeden önce |
articles.adopted | Çağrı | Bir tarama, sitenizin dışa aktardığı makaleleri içe aktardıktan sonra |
site.handover | Çağrı | Biri içerik türlerinin yazımını AutoPSEO'ya devrettiğinde veya geri aldığında |
Teslimatlar bir giden kutusu (outbox) üzerinden geçer: değişiklikle aynı veritabanı toplu işleminde kaydedilir, hemen gönderilir, yeniden denenir ve sitenizi bekleyebilir. Çağrılar ise düz istek ve yanıttır: AutoPSEO yanıtı anında okur. Yol komutlarının ne zaman gönderildiği Türlendirilmiş sayfalar ve yeni yollar sayfasında açıklanmıştır.
Makale payload'ı
article.published ve article.updated, tüm makaleyi, tüm diller dahil, taşır:
{
"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
}
}
}
Almanca girdi burada kısaltılmıştır; her çeviri copy ile aynı yapıya sahiptir.
| Alan | Anlamı |
|---|---|
id | AutoPSEO'nun makale kimliği. Asla değişmez, bu yüzden güvenle anahtar olarak kullanılabilir |
slug | Adresin son bölümü. Türlendirilmiş sayfalarda / ile birleştirilmiş bölümlerden oluşur, örneğin google/software-engineer |
path | Sitenize göre göreli adres: türün yolu artı slug. Sayfa oluşturulduktan sonra sabittir |
type | { key, name, path }. key kalıcıdır; path, türün makalelerinin bulunduğu yerdir (site kökü için boştur) |
lang, langs | Bu revizyonun ana dili ve tüm dilleri |
rev | Revizyon numarası |
url | AutoPSEO'nun bildiği tam adres: barındırma kapandığında sitenizin adresi ya da alıcınızın en son yanıtladığı url |
datePublished, dateModified | YYYY-MM-DD. Yayın tarihi sonraki revizyonlarda sabit kalır |
contentHash | Revizyonun parmak izi |
writer | Revizyonu kimin yazdığı: platform (AutoPSEO), mcp (kendi yapay zekanız), user, import (sitenizin kendi metni) veya aggregate (bir liste sayfası) |
copy | Makale, ana dilinde |
translations | Diğer diller, dil koduna göre anahtarlanmış |
category | Biçiminiz kategori tutuyorsa, makalenin sitenizdeki kategorisi |
topic | Makale sitenizin devrettiği bir konu için yazıldığında { externalId, keyword }; böylece o konuyu kapatabilirsiniz |
parts | Türlendirilmiş sayfalarda her bölümün değeri: { "company": "google", "role": "software-engineer" } |
kind | Bir liste sayfasında "list". Metni { title, h1, items: [{ from, text }] } biçimindedir; from, her öğenin geldiği sayfanın yoludur ve parts, toplanan değeri adlandırır |
cover | AutoPSEO'nun makale için ürettiği kapak. Bir kez indirip kendi kopyanızı saklayın; dosya adı görselin SHA-256 değeri olduğundan adres asla değişmez |
basedOn | article.updated içinde: bu revizyonun hangi sayfa sürümünden yazıldığı (aşağıdaki çakışmalara bakın) |
redirectTo | Birleştirme sonrası article.unpublished içinde: eski adresin artık nereye çıktığı. 301 ile yanıtlayın ve oraya yeni bir şey koymayın |
article.unpublished, copy, translations, category ve cover dışında aynı article alanlarını taşır.
copy içeriği
| Alan | Anlamı |
|---|---|
title, metaDescription, h1 | Sayfa başlığı, meta açıklaması ve ana başlık |
directAnswer | İlk gösterilen kısa yanıt |
keyTakeaways | Dizelerden oluşan bir liste |
sections | [{ heading, bodyHtml }] |
faq | [{ q, a }] |
sources | [{ title, url, lastVerified? }] |
question | İsteğe bağlı: biçiminiz istiyorsa, arayan kişinin kendi sözleriyle sorusu |
category | İsteğe bağlı: sitenizin kategorilerinden biri |
lists | İsteğe bağlı: bunu isteyen içerik türleri için (örneğin bir mülakat rehberinin örnek soruları) anahtarlara göre düz metin listeleri |
AutoPSEO'nun yazdığı makalelerde bodyHtml yalnızca p, ul, li, strong, em, a ve code etiketlerini kullanır; bağlantılar yalnızca href özniteliğini korur. Kendi sitenizden alınan metin HTML'inizi olduğu gibi korur. Şablonlarınız gerektiriyorsa bunu kendi temizleyicinizden de geçirin.
Alıcınızın yanıtladıkları
Herhangi bir 2xx yanıt bir teslimatı tamamlar. AutoPSEO ayrıca yanıt gövdesindeki isteğe bağlı bir JSON nesnesini okur; bunu 64 KB altında tutun, çünkü daha büyük bir yanıt başarısızlık sayılır.
| Alan | Etkisi |
|---|---|
url | Makalenin artık bulunduğu https:// adresi. AutoPSEO bunu makalenin URL'si olarak kaydeder |
version | Sayfanızın sürümü; en fazla 128 karakterlik anlamsız (opak) bir dizedir. Bir sonraki güncellemeyle basedOn olarak geri gelir |
urls | Bu teslimatın değiştirdiği https:// adresleri; IndexNow için kullanılır (her dil veya sunmayı bıraktığınız adresler). [] hiçbir şeyin değişmediği anlamına gelir |
cover | Makale için kapağınız yoksa ve teslimat da kapak taşımıyorsa "wanted". AutoPSEO bir kapak üretir ve bunu bir article.updated içinde gönderir |
Hata yanıtları farklı anlamlara gelir:
| Yanıtınız | AutoPSEO bunu nasıl okur | Ne olur |
|---|---|---|
{ "error": "unknown_event" } içeren bir 4xx veya bir 501 | Bu komutu henüz işlemiyorsunuz | Teslimat, yeniden denenmeden bekler |
{ "error": "conflict" } içeren bir 409 | Güncelleme, sayfanızın artık sahip olmadığı bir sürümden yazıldı | Teslimat yeniden denenmeden başarısız olur ve makale, bir sonraki değişikliğinden önce sitenizden yeniden okunur |
path.add veya path.remove için { "error": "<code>", "details": [] } içeren, 408 ya da 429 dışındaki bir 4xx | Gerekçenizi içeren bir ret | Kaydedilir; bkz. Türlendirilmiş sayfalar ve yeni yollar |
| Diğer her şey: 10 saniye içinde yanıt gelmemesi, ağ hatası, yönlendirme, 408, 429, 5xx veya başka bir 4xx | Geçici bir hata | Geri çekilme (backoff) ile yeniden denenir |
Yeniden denemeler ve geçersiz kılınan teslimatlar
Bir teslimat, değişiklik kaydedilir kaydedilmez gönderilir ve beş dakikalık bir zamanlama, vadesi gelen her şeyi en eskiden başlayarak gönderir. Geçici bir hatadan sonra AutoPSEO denemeler arasında sırasıyla 1 dakika, 5 dakika, 15 dakika, 1 saat, 3 saat, 6 saat ve 12 saat bekler. Sekizinci başarısız deneme teslimatı başarısız olarak işaretler ve Content sayfası bunu webhook'un yanında kırmızı bir rozetle sayar.
Aynı makale için daha yeni bir teslimat varsa, hâlâ bekleyen eski teslimat geçersiz kılındı (superseded) olarak işaretlenir ve hiç gönderilmez. Her teslimat makalenin tamamını taşıdığı için sitenizin ihtiyaç duyduğu tek şey en yeni teslimattır. Aynı değere ilişkin yol komutları da aynı şekilde çalışır.
Alıcınız bir teslimatı iki kez görebilir; örneğin makaleyi kaydettiği hâlde 10 saniyelik zaman aşımından sonra yanıt verdiğinde. X-AutoPSEO-Delivery üzerinden tekilleştirin ya da article.id ile saklayarak her yazmayı idempotent yapın. article.updated, bir article.unpublished sonrasında sildiğiniz bir makaleye de ulaşabilir, çünkü yayından kaldırılmış bir makaleyi yeniden yayımlamak bir güncelleme gönderir: güncellemeleri "oluştur veya değiştir" olarak ele alın.
Sitenizin henüz işlemediği komutlar
Bir site protokolü her seferinde tek bir komut olarak uygulayabilir. AutoPSEO her komutun durumunu tutar ve bunu üç yoldan öğrenir:
- Testler. Her başarılı ping'den sonra AutoPSEO komut başına bir
testgönderir. Gövde{ id, event: "test", occurredAt, site, test: { event: "<command>", ... } }biçimindedir; buradatest, gerçek komuta benzer yapıda bir örnek ve hiçbir sitede bulunmayan<type path>/autopseo-connection-testadresi hakkında bilgi taşır. Bunu gerçek komutu denetler gibi denetleyin, hiçbir şeyi değiştirmeyin ve bir 2xx ile yanıtlayın: Connected için{ "test": "<command>", "ok": true }, Not connected için{ "test": "<command>", "ok": false, "error": "unknown_event" }.ok: falseve başka bir hata koduyla verilen yanıt, örneğin sahip olmadığınız bir adresin yayından kaldırılması, yine de bağlı sayılır. Örnek her zamantestaltında yer alır, hiçbir zaman en üst düzeyde olmaz; böylece testlerden haberi olmayan bir alıcı içinde makale bulamaz. - Gerçek teslimatlar ve çağrılar. Bir 2xx komutu bağlı olarak işaretler;
unknown_eventkomutu bağlı değil olarak işaretler. - Ping yanıtı. Yanıtınızın
autopseo.eventsalanında listelediği komutlar, bir test veya teslimat daha fazlasını söyleyene kadar Declared, not tested olarak görünür.
Bağlı olmayan bir komuta ait teslimat bekler. Content sayfası bekleyenleri listeler, örneğin "Your site does not take articles down yet"; elle yaptığınız her şey için bir Done on the site düğmesi bulunur ve AutoPSEO o teslimatı bir daha göndermez. Sonraki bir test veya teslimat komutu bağlı bulduğunda, onu bekleyen her şey gönderilir.
Ping yanıtı
ping isteğine bir 2xx ile yanıt verin. Yalnızca makale alan bir site için başka bir şey gerekmez. Sitenizin başka ne yaptığını AutoPSEO'ya bildirmek için bir autopseo nesnesi ekleyin:
{
"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"
}
}
| Alan | Anlamı |
|---|---|
version | 1. Sürüm 1 veya daha yüksek değilse nesne yok sayılır |
events | İşlediğiniz komutlar; test edilene kadar bildirilmiş olarak görünür |
covers | Sayfalarınız her makaleyle birlikte bir kapak gösteriyorsa true. Bu durumda AutoPSEO'nun yazarı kapağı yazarken üretir, böylece ilk teslimat kapağı taşır ve kendi yapay zekanızdan da bir kapak sağlaması istenir. Bkz. Kapaklar |
indexNow | { key, keyLocation? }: sunduğunuz IndexNow anahtarı (8 ila 128 harf, rakam ve tire) ve anahtar dosyası /<key>.txt konumunda değilse https:// konumu |
export | site.export işleminin teslim ettiği veriler: articles, topics, profile |
adopt | articles.adopted isteğini kabul edersiniz; böylece tam bir tarama mevcut makalelerinizi devralabilir |
handover | site.handover isteğini kabul edersiniz |
types | Kabul ettiğiniz içerik türü anahtarları (en fazla 50) |
writer | Yeni makalelerinizi bugün kimin yazdığı: site veya autopseo |
Sunduğunuz adresler için IndexNow
Ping yanıtınız indexNow bildirdiğinde, AutoPSEO kabul ettiğiniz teslimatlardan sonra api.indexnow.org adresinde IndexNow'a ping gönderir ve siteniz kendisi hiçbir şey göndermez.
- Yanıtınızın listelediği
urlsadreslerini gönderir.urlsyoksa yanıtınızınurldeğerini, o da yoksa makalenin URL'sini gönderir.urls: []hiçbir şey göndermez. - IndexNow protokolünün gerektirdiği gibi yalnızca kendi ana bilgisayarınızdaki adresler dahil edilir:
keyLocationdeğerinin ana bilgisayarı, yoksa sitenizin adresinin ana bilgisayarı. - Her teslimat turu, site başına en fazla 10.000 adresli tek bir ping gönderir.
- 200 ve 202 kabul edilir. Bir 429, bir 5xx veya yanıt gelmemesi adresleri saklar ve bir saat sonra, her seferinde iki katına çıkarak en fazla bir güne kadar (
Retry-Afterdaha fazlasını istiyorsa daha uzun) yeniden dener; sonraki teslimatların eklediği adresler de buna dahil edilir. Bir 403 (anahtar geçerli değil) veya 422 (adresler ana bilgisayara ait değil) kaydedilir ve atılır. - Content sayfası son ping'i gösterir: ne zaman, kaç adres, HTTP durumu ve bir sonraki deneme.
- AutoPSEO makaleleri de barındırırken, barındırılan sayfalar AutoPSEO'nun kendi anahtarıyla ping'lenir ve sizinki kullanılmaz.
Indexing altındaki IndexNow aracı ayrı, elle çalışan bir göndericidir; bkz. IndexNow.
Çakışmalar: sitenizin metni özgün olandır
Yanıtınızın version değeri (veya dışa aktarımınızdaki bir makalenin version değeri), AutoPSEO'nun sayfanızın hangi sürümde olduğuna inandığı şeydir. Her article.updated bunu basedOn olarak taşır. Sayfa o zamandan beri sitenizde değiştiyse, örneğin bir editörün düzeltmesiyle, { "error": "conflict" } içeren bir 409 ile yanıt verin ve hiçbir şey saklamayın. Teslimat yeniden denenmeden başarısız olur.
Yayındaki bir makalede kuyruğa alınmış bir değişiklik yazılmadan önce, sitenizde site.export işleniyorsa AutoPSEO o makaleyi site.export ve paths ile sitenizden yeniden okur. Sürümünüz ilerlemişse, metniniz makalenin bir sonraki revizyonu olur (yazar import) ve ardından gelen güncelleme, mevcut sürümünüzü basedOn olarak taşır.
path.add ve path.remove
Bu iki teslimat, türlendirilmiş bir içerik türünün tek bir değerini ilgilendirir; örneğin /interviews/{company}/{role} içindeki bir şirket. article yerine bir path nesnesi taşırlar:
{
"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"] }
}
}
| Alan | Anlamı |
|---|---|
path | Değerin sitenizdeki kendi adresi |
type | İçerik türü, { key, name, path } olarak |
part | Değerin doldurduğu adres bölümü |
value | { value, name }; path.add içinde ayrıca AutoPSEO'nun yazarının topladığı aliases, url, sameAs, description ve facts. Her olgunun text, source, lastVerified ve official ya da reported değerli bir tier alanı vardır |
takes | path.add içinde: insanların bununla birlikte aradığı diğer bölümlerin değerleri |
redirectTo | path.remove içinde: değerin adreslerinin bundan sonra 301 ile nereye yönlendirilmesi gerektiği |
Komutu aldıysanız bir 2xx ile yanıt verin; path.add için değerin artık bulunduğu url adresini, path.remove için ise sunmayı bıraktığınız ve IndexNow'a giden urls adreslerini ekleyebilirsiniz. Reddetmek için { "error": "<code>", "details": ["<why>"] } içeren bir 4xx ile yanıt verin. path.add için invalid_value, değeri nedeninizle birlikte yazara geri gönderir (üçüncü ret kesindir); başka herhangi bir kod değeri kalıcı olarak reddeder. path.remove için not_found tamamlanmış sayılır; 409 in_use gibi başka herhangi bir kod her şeyi olduğu gibi bırakır. Bekleyen bir yol komutunu Done on the site olarak işaretlemek, sitenizin evet yanıtı sayılır.
Çağrılar: AutoPSEO'nun yanıtını okuduğu istekler
Çağrılar, teslimatlar gibi imzalanır. Bir çağrı varsayılan olarak 60 saniye, yeniden okumalar ve yol kontrolleri için 30 saniye bekler ve 24 MB'a kadar yanıt okur. 2xx dışındaki her yanıt çağrıyı başarısız kılar.
site.export
İstek export: { what, cursor, limit } alanını taşır; konular için ayrıca since (bir ISO zamanı) veya yeniden okuma için paths bulunur. { "items": [], "next": "<cursor>" } ile yanıt verin; AutoPSEO, next değeri boş veya null olana kadar cursor alanını sizin next değerinize ayarlayarak yeniden sorar.
what: "articles",exportiçindearticlesveadopt: truebildirdiğinizde tam site taramalarında, sayfa başına 10 öğeyle çalışır. 400 yanıtı "desteklenmiyor" anlamına gelir.paths: ["/answers/x"]velimit: 1ile tek bir makale yeniden okunur; yalnızca o makaleyle yanıt verin.what: "topics"her taramada ve günlük eşitlemede, sayfa başına 50 öğeyle çalışır; günlük eşitleme, son çekimin zamanı olansincealanını gönderir.what: "profile"her taramada çalışır.{ "profile": { types, languages, rules, categories, format } }ile yanıt verin; hepsi isteğe bağlıdır:typesiçin[{ key, name, path }]verulesiçinbrandTerms,synonyms,noise,onTopicveoffTopic.
Dışa aktarılan bir makale için externalId, bir slug (herhangi bir yazı sisteminden harfler ve rakamlar ile tireler, en fazla 200 karakter, bölümler / ile birleştirilmiş), bu slug ile biten bir path ve en az title, h1 ile bir bölüm içeren copy gerekir. İsteğe bağlı alanlar: lang (varsayılan en), type (bir içerik türü anahtarı), url, keyword, category, datePublished, dateModified, version, translations, owner (devralındıktan sonra AutoPSEO'nun makale kimliği) ve ilgili görevi kuyruğa alan maintenance: { kind: "refresh" or "optimize", reason }. Dışa aktarılan her makale, ilk revizyonu sizin metniniz olan yönetilen bir makale olur ve geriye hiçbir şey gönderilmez. Yeniden dışa aktarmak güvenlidir ve AutoPSEO'nun yayından kaldırdığı bir makale dışa aktarım yoluyla asla geri gelmez.
Bir konu öğesi { keyword, status?, volume?, source?, externalId?, type?, reason? } biçimindedir; status değeri queued, planned veya rejected olabilir.
articles.adopted
İçe aktarmanın ardından AutoPSEO, yeni makaleleri yönetmesine izin vermenizi ister: { "articles": [{ "externalId", "articleId", "slug", "path" }] }. { "adopted": ["<externalId>"], "refused": [{ "externalId": "<id>", "reason": "<why>" }] } ile yanıt verin. Onaylamadığınız bir makale, AutoPSEO onun için henüz bir revizyon yazmadıysa, AutoPSEO'dan yeniden kaldırılır.
site.handover
{ "handover": { "types": ["answers"], "writer": "autopseo" } }; biri mülkün Your site sayfasında Let AutoPSEO write them veya Hand writing back to the site seçeneğini seçtiğinde gönderilir. { "ok": true, "writer": "autopseo" } ile yanıt verin. AutoPSEO'ya devrin ardından siteniz, onayladığı konuları kendisi yazmak yerine AutoPSEO'ya iletir.
path.check
{ "paths": [{ "path": "/interviews/google/data-engineer" }, { "path": "/interviews/jane-street", "new": { "part": "company", "value": "jane-street", "name": "Jane Street" } }] }. Her yol için bir sonuç döndürün: { "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."] }] }. Bir ret, gerekçenizle birlikte saklanır ve bir daha sorulmaz; dışarıda bıraktığınız bir yol daha sonra yeniden sorulur.
Asgari bir alıcı
İmzayı Web Crypto API ile doğrulayan, üç makale komutunu işleyen ve geri kalanını unknown_event olarak reddeden bir Cloudflare Worker. Makaleleri ARTICLES olarak bağlanmış bir KV namespace'inde, imzalama gizli anahtarını ise AUTOPSEO_SECRET adlı bir Worker gizli anahtarında tutar.
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);
},
};
Node.js üzerinde aynı doğrulamayı, çerçevenizin aldığı ham gövde üzerinden createHmac ve timingSafeEqual ile yapın:
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));
}
Sırada: webhook'a nelerin ulaşacağını yazım ve inceleme ile bakım belirler; akışın tamamı içerik motoru genel bakışında yer alır.