Lewati ke konten
Dokumentasi

Webhook pengiriman: menerima artikel di situs Anda sendiri

Webhook bertanda tangan yang mengirim artikel AutoPSEO ke situs Anda: header, setiap event beserta payload-nya, respons yang harus diberikan, percobaan ulang, perintah yang menunggu, dan IndexNow.

Properti → Konten → Tempat artikel dipublikasikanBaca 18 mnt

AutoPSEO mengirim setiap artikel yang diterbitkan, diperbarui, atau diturunkan ke situs Anda sendiri sebagai POST HTTPS bertanda tangan. Situs Anda menyimpan artikel tersebut dan merendernya dengan template-nya sendiri, sehingga konten berada di sistem Anda dan tetap berfungsi seandainya suatu saat Anda berhenti menggunakan AutoPSEO. Halaman ini adalah referensi bagi developer yang membangun penerimanya: penandatanganan, setiap event beserta payload-nya, respons yang dibaca AutoPSEO, percobaan ulang, perintah yang menunggu, dan IndexNow.

Menyiapkan webhook

  1. Buka halaman Konten milik properti, lalu masuk ke Tempat artikel diterbitkan.
  2. Isi Alamat webhook di situs Anda. Alamat ini harus berupa URL https:// pada nama host publik: alamat IP dan localhost ditolak. AutoPSEO tidak mengikuti pengalihan, jadi berikan alamat akhirnya.
  3. Isi Alamat situs Anda, misalnya https://example.com. Path setiap tipe konten berada di bawahnya, sehingga artikel bertipe /answers berada di https://example.com/answers/<slug>.
  4. Pilih Simpan dan kirim tes. AutoPSEO membuat secret penandatanganan (whsec_ diikuti 48 karakter heksadesimal) dan mengirim ping ke alamat Anda. Penerima yang memeriksa tanda tangan akan gagal pada ping pertama ini, karena belum memiliki secret-nya.
  5. Tampilkan Secret penandatanganan dengan Tampilkan atau Salin, simpan sebagai secret di situs Anda, lalu pilih Kirim tes.
  6. Baca Yang ditangani situs Anda: setiap perintah menampilkan Terhubung, Tidak terhubung, Dideklarasikan, belum diuji, atau Belum diuji.

Properti baru dimulai dengan AutoPSEO yang menghosting artikel di alamatnya sendiri (lihat Hosting), dan hosting serta webhook dapat berjalan berdampingan. Setelah situs Anda menampilkan artikel, pilih Berhenti menghosting di AutoPSEO: setiap artikel yang tayang dipindahkan ke alamat situs Anda dan dikirim ke webhook Anda sebagai article.published. Tombol itu memerlukan webhook yang menjawab tes terakhirnya serta alamat situs Anda. Selama hosting nonaktif, webhook tidak dapat dihapus.

Secret baru langsung menggantikan secret penandatanganan dan secret lama berhenti terverifikasi seketika. Pasang secret baru di situs Anda segera setelah dibuat.

AutoPSEO hanya mengantrekan pengiriman untuk webhook yang ping terakhirnya dijawab dengan 2xx. Ping yang gagal (saat menyimpan, saat Kirim tes, atau di awal pemindaian situs) menandai webhook sebagai bermasalah, dan artikel yang diterbitkan selama webhook bermasalah tidak diantrekan untuknya. Pastikan ping tetap dijawab, lalu pilih Kirim tes setelah Anda memperbaiki gangguan.

Header dan tanda tangan

Setiap permintaan adalah POST dengan body JSON dan header berikut:

HeaderNilai
Content-Typeapplication/json
User-AgentAutoPSEO-Webhook/1
X-AutoPSEO-EventNama event, yang juga ada di body sebagai event
X-AutoPSEO-DeliveryID pengiriman, yang juga ada di body sebagai id. Sama pada setiap percobaan ulang satu pengiriman
X-AutoPSEO-TimestampWaktu Unix dalam detik saat percobaan ini ditandatangani
X-AutoPSEO-Signaturesha256= diikuti HMAC-SHA256 heksadesimal dari <timestamp>.<raw body>

Untuk memverifikasi permintaan:

  1. Ambil header timestamp, tanda titik, dan body permintaan mentah persis seperti yang diterima, sebelum parsing JSON apa pun.
  2. Hitung HMAC-SHA256 atas string tersebut. Kuncinya adalah seluruh secret penandatanganan seperti yang ditampilkan aplikasi, termasuk prefiks whsec_, sebagai byte UTF-8.
  3. Enkode hasilnya ke heksadesimal dan bandingkan dengan bagian X-AutoPSEO-Signature setelah sha256=, dalam waktu konstan. Tolak permintaan jika keduanya berbeda.

Setiap percobaan ditandatangani ulang dengan timestamp-nya sendiri, sehingga Anda dapat menolak permintaan yang timestamp-nya jauh berbeda dari jam Anda (lima menit adalah jendela yang umum) tanpa mengganggu percobaan ulang. HMAC sendiri didefinisikan dalam RFC 2104.

Setiap body memuat id, event, occurredAt (ISO 8601, UTC), dan site: { propertyId, domain }.

Event

EventJenisKapan AutoPSEO mengirimnya
pingPemeriksaanSaat Anda menyimpan webhook, saat Kirim tes, dan di awal setiap pemindaian situs
testPemeriksaanTepat setelah ping berhasil, satu untuk setiap perintah
article.publishedPengirimanArtikel tayang untuk pertama kalinya; setiap artikel yang tayang saat Anda berhenti menghosting
article.updatedPengirimanRevisi baru tayang (penyegaran, optimisasi, rollback, penerbitan ulang setelah penurunan, penambahan bahasa) atau cover ditambahkan
article.unpublishedPengirimanArtikel diturunkan, atau digabungkan ke halaman lain
path.addPengirimanNilai baru dari tipe konten bertipe disetujui
path.removePengirimanSeseorang meminta path suatu nilai diturunkan
path.checkPanggilanSebelum AutoPSEO merencanakan halaman di alamat yang belum ada halamannya di situs Anda, atau mengusulkan nilai baru
site.exportPanggilanSaat pemindaian situs, dalam sinkronisasi topik harian, dan sebelum AutoPSEO mengubah artikel yang tayang
articles.adoptedPanggilanSetelah pemindaian mengimpor artikel yang diekspor situs Anda
site.handoverPanggilanSaat seseorang menyerahkan penulisan tipe konten ke AutoPSEO, atau mengembalikannya

Pengiriman melewati outbox: dicatat dalam batch database yang sama dengan perubahannya, dikirim seketika, dicoba ulang, dan dapat menunggu situs Anda. Panggilan adalah permintaan dan respons biasa: AutoPSEO membaca jawabannya saat itu juga. Kapan perintah path dikirim dijelaskan di Halaman bertipe dan path baru.

Payload artikel

article.published dan article.updated membawa seluruh artikel, termasuk semua bahasa:

{
  "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
    }
  }
}

Entri bahasa Jerman dipersingkat di sini; setiap terjemahan memiliki bentuk yang sama dengan copy.

FieldArti
idID artikel di AutoPSEO. ID ini tidak pernah berubah, sehingga aman dipakai sebagai kunci
slugBagian terakhir dari alamat. Pada halaman bertipe, isinya segmen yang digabung dengan /, seperti google/software-engineer
pathAlamat relatif terhadap situs Anda: path tipe ditambah slug. Tetap setelah halaman ada
type{ key, name, path }. key stabil; path adalah tempat artikel tipe itu berada (kosong untuk root situs)
lang, langsBahasa utama dan semua bahasa dari revisi ini
revNomor revisi
urlAlamat lengkap sebagaimana diketahui AutoPSEO: alamat situs Anda setelah hosting nonaktif, atau url yang terakhir dijawab penerima Anda
datePublished, dateModifiedYYYY-MM-DD. Tanggal terbit tetap sama sepanjang revisi berikutnya
contentHashSidik jari revisi
writerSiapa yang menulis revisi: platform (AutoPSEO), mcp (AI Anda sendiri), user, import (teks asli situs Anda) atau aggregate (halaman daftar)
copyArtikel dalam bahasa utamanya
translationsBahasa lainnya, dengan kunci kode bahasa
categoryKategori situs Anda untuk artikel ini, jika format Anda menyimpan kategori
topic{ externalId, keyword } jika artikel ditulis untuk topik yang diserahkan situs Anda, sehingga Anda dapat menutup topik tersebut
partsPada halaman bertipe, nilai setiap bagian: { "company": "google", "role": "software-engineer" }
kind"list" pada halaman daftar. Copy-nya berbentuk { title, h1, items: [{ from, text }] }, dengan from adalah path halaman asal setiap item, dan parts menyebutkan nilai yang dihimpunnya
coverCover yang dibuat AutoPSEO untuk artikel. Ambil sekali dan simpan salinan Anda sendiri; alamatnya tidak pernah berubah karena nama file adalah SHA-256 gambar
basedOnPada article.updated: versi halaman Anda yang menjadi dasar penulisan revisi ini (lihat konflik di bawah)
redirectToPada article.unpublished setelah penggabungan: tujuan alamat lama sekarang. Jawab dengan 301 dan jangan menaruh apa pun yang baru di sana

article.unpublished membawa field article yang sama kecuali copy, translations, category dan cover.

Isi copy

FieldArti
title, metaDescription, h1Judul halaman, meta description, dan heading
directAnswerJawaban singkat yang ditampilkan pertama
keyTakeawaysDaftar string
sections[{ heading, bodyHtml }]
faq[{ q, a }]
sources[{ title, url, lastVerified? }]
questionOpsional: pertanyaan pencari dengan kata-katanya sendiri, jika format Anda memintanya
categoryOpsional: salah satu kategori situs Anda
listsOpsional: daftar teks biasa menurut kunci, untuk tipe konten yang memintanya (contoh pertanyaan dalam panduan wawancara)

Pada artikel yang ditulis AutoPSEO, bodyHtml hanya memakai p, ul, li, strong, em, a dan code, dan tautan hanya mempertahankan href-nya. Teks yang diambil dari situs Anda sendiri mempertahankan HTML Anda. Jalankan juga melalui sanitizer Anda sendiri jika template Anda mengharuskannya.

Respons yang diberikan penerima Anda

Setiap 2xx menyelesaikan pengiriman. AutoPSEO juga membaca objek JSON opsional dari body respons; jaga agar tetap di bawah 64 KB, karena respons yang lebih besar dihitung sebagai kegagalan.

FieldEfek
urlAlamat https:// tempat artikel kini berada. AutoPSEO mencatatnya sebagai URL artikel
versionVersi halaman Anda, string buram sepanjang maksimal 128 karakter. Dikirim kembali sebagai basedOn pada pembaruan berikutnya
urlsAlamat https:// yang berubah akibat pengiriman ini, untuk IndexNow (setiap bahasa, atau alamat yang tidak lagi Anda sajikan). [] berarti tidak ada yang berubah
cover"wanted" jika Anda tidak punya cover untuk artikel dan pengiriman tidak membawa cover. AutoPSEO membuatnya dan mengirimkannya dalam article.updated

Respons error memiliki arti berbeda:

Respons AndaDibaca AutoPSEO sebagaiYang terjadi
4xx dengan { "error": "unknown_event" }, atau 501Anda belum menangani perintah iniPengiriman menunggu, tanpa percobaan ulang
409 dengan { "error": "conflict" }Pembaruan ditulis dari versi yang tidak lagi dimiliki halaman AndaPengiriman gagal tanpa percobaan ulang, dan artikel dibaca ulang dari situs Anda sebelum perubahan berikutnya
4xx selain 408 atau 429 dengan { "error": "<code>", "details": [] } untuk path.add atau path.removePenolakan beserta alasan AndaDicatat; lihat Halaman bertipe dan path baru
Lainnya: tidak ada jawaban dalam 10 detik, error jaringan, pengalihan, 408, 429, 5xx atau 4xx lainnyaKegagalan sementaraDicoba ulang dengan backoff

Percobaan ulang dan pengiriman yang digantikan

Pengiriman dikirim begitu perubahan disimpan, dan jadwal lima menit mengirim apa pun yang sudah waktunya, dimulai dari yang paling lama. Setelah kegagalan sementara, AutoPSEO menunggu 1 menit, lalu 5 menit, 15 menit, 1 jam, 3 jam, 6 jam, dan 12 jam di antara percobaan. Percobaan gagal kedelapan menandai pengiriman sebagai gagal, dan halaman Konten menghitungnya dalam badge merah di samping webhook.

Jika ada pengiriman yang lebih baru untuk artikel yang sama, pengiriman lama yang masih tertunda ditandai sebagai digantikan dan tidak pernah dikirim. Setiap pengiriman membawa seluruh artikel, jadi pengiriman terbaru sudah cukup bagi situs Anda. Perintah path untuk nilai yang sama bekerja dengan cara yang sama.

Penerima Anda bisa melihat satu pengiriman dua kali, misalnya ketika artikel sudah disimpan tetapi jawabannya datang setelah batas waktu 10 detik. Lakukan deduplikasi berdasarkan X-AutoPSEO-Delivery, atau buat setiap penulisan idempoten dengan menyimpan berdasarkan article.id. article.updated juga dapat mencapai artikel yang sudah Anda hapus setelah article.unpublished, karena menerbitkan ulang artikel yang diturunkan mengirim pembaruan: perlakukan pembaruan sebagai "buat atau ganti".

Perintah yang belum ditangani situs Anda

Situs dapat mengimplementasikan protokol ini satu perintah demi satu. AutoPSEO menyimpan status setiap perintah dan mengetahuinya melalui tiga cara:

  • Tes. Setelah setiap ping yang berhasil, AutoPSEO mengirim satu test per perintah. Body-nya { id, event: "test", occurredAt, site, test: { event: "<command>", ... } }, dengan test berisi contoh yang berbentuk seperti perintah sebenarnya dan tentang <type path>/autopseo-connection-test, alamat yang tidak dimiliki situs mana pun. Periksa seperti Anda memeriksa perintah sebenarnya, jangan ubah apa pun, dan jawab dengan 2xx: { "test": "<command>", "ok": true } untuk Terhubung, { "test": "<command>", "ok": false, "error": "unknown_event" } untuk Tidak terhubung. Jawaban dengan ok: false dan kode error lain, seperti penurunan alamat yang tidak Anda miliki, tetap dihitung sebagai terhubung. Contoh itu berada di bawah test, tidak pernah di tingkat atas, sehingga penerima yang tidak tahu apa-apa tentang tes tidak akan menemukan artikel di dalamnya.
  • Pengiriman dan panggilan sebenarnya. 2xx menandai perintah sebagai terhubung; unknown_event menandainya sebagai tidak terhubung.
  • Jawaban ping. Perintah yang Anda cantumkan di autopseo.events tampil sebagai Dideklarasikan, belum diuji sampai tes atau pengiriman memberi kepastian lebih lanjut.

Pengiriman untuk perintah yang tidak terhubung akan menunggu. Halaman Konten mencantumkan apa yang menunggu, misalnya "Situs Anda belum menurunkan artikel", dengan tombol Selesai di situs untuk hal yang Anda kerjakan secara manual; AutoPSEO lalu tidak pernah mengirim yang satu itu. Ketika tes atau pengiriman berikutnya mendapati perintah itu terhubung, semua yang menunggu di perintah tersebut dikirim.

Jawaban ping

Jawab ping dengan 2xx. Situs yang hanya menerima artikel tidak membutuhkan yang lain. Untuk memberi tahu AutoPSEO apa lagi yang dilakukan situs Anda, sertakan objek autopseo:

{
  "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"
  }
}
FieldArti
version1. Objek diabaikan tanpa versi 1 atau lebih
eventsPerintah yang Anda tangani, ditampilkan sebagai dideklarasikan sampai diuji
coverstrue jika halaman Anda menampilkan cover bersama setiap artikel. Penulis AutoPSEO lalu membuat cover saat menulis, sehingga pengiriman pertama sudah membawanya, dan AI Anda sendiri diminta menyediakannya. Lihat Cover
indexNow{ key, keyLocation? }: key IndexNow yang Anda sajikan (8 sampai 128 huruf, angka, dan tanda hubung) dan, jika file key tidak berada di /<key>.txt, lokasi https://-nya
exportApa yang diserahkan site.export: articles, topics, profile
adoptAnda menerima articles.adopted, sehingga pemindaian penuh dapat mengambil alih artikel Anda yang sudah ada
handoverAnda menerima site.handover
typesKunci tipe konten yang Anda terima (hingga 50)
writerSiapa yang saat ini menulis artikel baru Anda: site atau autopseo

IndexNow untuk alamat yang Anda sajikan

Ketika jawaban ping Anda mendeklarasikan indexNow, AutoPSEO melakukan ping ke IndexNow di api.indexnow.org setelah pengiriman yang Anda terima, dan situs Anda sendiri tidak perlu melakukan ping apa pun.

  • AutoPSEO mengirim urls yang dicantumkan jawaban Anda. Tanpa urls, AutoPSEO mengirim url dari jawaban Anda, jika tidak ada maka URL artikel. urls: [] tidak mengirim apa pun.
  • Hanya alamat di host Anda sendiri yang dimasukkan, sebagaimana disyaratkan protokol IndexNow: host dari keyLocation, jika tidak ada maka host dari alamat situs Anda.
  • Setiap putaran pengiriman mengirim satu ping per situs, dengan hingga 10.000 alamat.
  • 200 dan 202 diterima. 429, 5xx, atau tidak ada jawaban membuat alamat tetap disimpan dan dicoba lagi setelah satu jam, berlipat ganda setiap kali hingga sehari (lebih lama jika Retry-After memintanya), bersama alamat yang ditambahkan pengiriman berikutnya. 403 (key tidak valid) atau 422 (alamat tidak berada di host) dicatat dan dibuang.
  • Halaman Konten menampilkan ping terakhir: kapan, berapa banyak alamat, status HTTP, dan percobaan berikutnya.
  • Selama AutoPSEO juga menghosting artikel, halaman yang dihosting di-ping dengan key milik AutoPSEO sendiri, dan key Anda tidak dipakai.

Alat IndexNow di bawah Pengindeksan adalah pengirim manual yang terpisah; lihat IndexNow.

Konflik: teks situs Anda adalah aslinya

version pada jawaban Anda (atau version sebuah artikel dalam ekspor Anda) adalah versi yang diyakini AutoPSEO sebagai posisi halaman Anda. Setiap article.updated membawanya sebagai basedOn. Jika halaman berubah di situs Anda sejak itu, misalnya setelah koreksi dari editor, jawab 409 dengan { "error": "conflict" } dan jangan simpan apa pun. Pengiriman gagal tanpa percobaan ulang.

Sebelum perubahan yang diantrekan pada artikel yang tayang ditulis, AutoPSEO membaca ulang artikel itu dari situs Anda dengan site.export dan paths, jika situs Anda menangani site.export. Jika versi Anda sudah berubah, teks Anda menjadi revisi berikutnya dari artikel (writer import), dan pembaruan yang menyusul membawa versi Anda saat ini sebagai basedOn.

path.add dan path.remove

Kedua pengiriman ini menyangkut satu nilai dari tipe konten bertipe, misalnya sebuah perusahaan dalam /interviews/{company}/{role}. Keduanya membawa objek path alih-alih 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"] }
  }
}
FieldArti
pathAlamat nilai itu sendiri di situs Anda
typeTipe konten, sebagai { key, name, path }
partBagian alamat yang diisi oleh nilai
value{ value, name }, ditambah pada path.add berupa aliases, url, sameAs, description dan facts yang dikumpulkan penulis AutoPSEO. Setiap fakta memiliki text, source, lastVerified dan tier berupa official atau reported
takesPada path.add: nilai bagian lain yang dicari orang bersamaan dengannya
redirectToPada path.remove: tujuan alamat nilai itu mulai sekarang, dengan 301

Jawab 2xx jika Anda menerima perintah; pada path.add Anda dapat menambahkan url tempat nilai itu kini berada, dan pada path.remove urls yang tidak lagi Anda sajikan, yang dikirim ke IndexNow. Untuk menolak, jawab 4xx dengan { "error": "<code>", "details": ["<why>"] }. Pada path.add, invalid_value mengembalikan nilai itu ke penulis beserta alasan Anda (penolakan ketiga bersifat final) dan kode lain menolaknya untuk selamanya. Pada path.remove, not_found dihitung selesai, dan kode lain, seperti 409 in_use, membiarkan semuanya apa adanya. Menandai perintah path yang menunggu sebagai Selesai di situs dihitung sebagai persetujuan dari situs Anda.

Panggilan: permintaan yang jawabannya dibaca AutoPSEO

Panggilan ditandatangani seperti pengiriman. Sebuah panggilan menunggu hingga 60 detik secara default dan 30 detik untuk pembacaan ulang dan pemeriksaan path, serta membaca jawaban hingga 24 MB. Jawaban apa pun selain 2xx membuat panggilan gagal.

site.export

Permintaan membawa export: { what, cursor, limit }, ditambah since (waktu ISO) untuk topik atau paths untuk pembacaan ulang. Jawab { "items": [], "next": "<cursor>" }; AutoPSEO bertanya lagi dengan cursor diisi next Anda sampai kosong atau null.

  • what: "articles" berjalan pada pemindaian situs penuh, 10 per halaman, ketika Anda mendeklarasikan export dengan articles dan adopt: true. Jawaban 400 berarti "tidak didukung". Dengan paths: ["/answers/x"] dan limit: 1, satu artikel dibaca ulang; jawab dengan artikel itu saja.
  • what: "topics" berjalan pada setiap pemindaian dan dalam sinkronisasi harian, 50 per halaman; sinkronisasi harian mengirim since, waktu penarikan terakhir.
  • what: "profile" berjalan pada setiap pemindaian. Jawab { "profile": { types, languages, rules, categories, format } }, semuanya opsional: types sebagai [{ key, name, path }], dan rules dengan brandTerms, synonyms, noise, onTopic dan offTopic.

Artikel yang diekspor memerlukan externalId, slug (huruf dan angka dari skrip apa pun serta tanda hubung, maksimal 200 karakter, segmen digabung dengan /), path yang diakhiri slug itu, dan copy dengan setidaknya title, h1 dan satu section. Field opsional: lang (default en), type (kunci tipe konten), url, keyword, category, datePublished, dateModified, version, translations, owner (ID artikel AutoPSEO, setelah diadopsi) dan maintenance: { kind: "refresh" or "optimize", reason }, yang mengantrekan tugas tersebut. Setiap artikel yang diekspor menjadi artikel terkelola dengan teks Anda sebagai revisi pertamanya, dan tidak ada yang dikirim kembali. Mengekspor ulang aman dilakukan, dan artikel yang telah diturunkan AutoPSEO tidak pernah kembali melalui ekspor.

Item topik adalah { keyword, status?, volume?, source?, externalId?, type?, reason? }, dengan status berupa queued, planned atau rejected.

articles.adopted

Setelah mengimpor, AutoPSEO meminta Anda mengizinkannya mengelola artikel baru: { "articles": [{ "externalId", "articleId", "slug", "path" }] }. Jawab { "adopted": ["<externalId>"], "refused": [{ "externalId": "<id>", "reason": "<why>" }] }. Artikel yang tidak Anda konfirmasi dihapus lagi dari AutoPSEO, kecuali AutoPSEO sudah menulis revisi darinya.

site.handover

{ "handover": { "types": ["answers"], "writer": "autopseo" } }, dikirim ketika seseorang memilih Biarkan AutoPSEO menulisnya atau Kembalikan penulisan ke situs di halaman Situs Anda milik properti. Jawab { "ok": true, "writer": "autopseo" }. Setelah penyerahan ke AutoPSEO, situs Anda meneruskan topik yang disaringnya ke AutoPSEO alih-alih menulisnya sendiri.

path.check

{ "paths": [{ "path": "/interviews/google/data-engineer" }, { "path": "/interviews/jane-street", "new": { "part": "company", "value": "jane-street", "name": "Jane Street" } }] }. Jawab satu hasil per 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."] }] }. Penolakan disimpan beserta alasan Anda dan tidak ditanyakan lagi; path yang Anda lewatkan akan ditanyakan lagi nanti.

Penerima minimal

Sebuah Cloudflare Worker yang memverifikasi tanda tangan dengan Web Crypto API, menangani tiga perintah artikel, dan menolak sisanya sebagai unknown_event. Worker ini menyimpan artikel di namespace KV yang di-bind sebagai ARTICLES, dan secret penandatanganan di secret Worker bernama 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);
  },
};

Di Node.js, verifikasi dengan cara yang sama menggunakan createHmac dan timingSafeEqual, atas body mentah yang diterima framework Anda:

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));
}

Selanjutnya: apa yang sampai ke webhook ditentukan oleh penulisan dan peninjauan serta pemeliharaan; alur lengkapnya ada di ikhtisar content engine.

Jalankan konten SEO Anda secara otomatis

Hubungkan Search Console dan situs Anda. AutoPSEO memindainya dan langsung merencanakan topik pertama. Tanpa kartu kredit.